PDF to TIFF script

I wrote the following script to convert a single- or multi-page PDF to a TIFF, and it works as expected with one exception. If the PDF is a document with selectable text, the TIFF created by the script has a dark background. I thought changing NSDeviceRGBColorSpace to something else might help but it didn’t. Thanks for any help.

use framework "AppKit"
use framework "Foundation"
use framework "PDFKit"
use scripting additions

set theResolution to 300
set thePDF to POSIX path of (choose file of type {"com.adobe.pdf"})
set theTIFF to getTiffPath(thePDF)
pdfToTiff(thePDF, theTIFF, theResolution)

on getTiffPath(thePDF)
	set thePDF to current application's |NSURL|'s fileURLWithPath:thePDF
	set fileNoExtension to thePDF's URLByDeletingPathExtension
	return fileNoExtension's URLByAppendingPathExtension:"tiff"
end getTiffPath

on pdfToTiff(thePDF, theTIFF, theResolution) -- heavily borrows from a script by Shane Stanley
	set thePDF to current application's |NSURL|'s fileURLWithPath:thePDF
	set theDocument to (current application's PDFDocument's alloc()'s initWithURL:thePDF)
	set pdfPageCount to theDocument's pageCount()
	set tiffArray to current application's NSMutableArray's new()
	repeat with i from 0 to (pdfPageCount - 1)
		set aPage to (theDocument's pageAtIndex:i)
		set pageSize to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
		set pageWidth to current application's NSWidth(pageSize)
		set pageHeight to current application's NSHeight(pageSize)
		set pixelWidth to (pageWidth * theResolution / 72) div 1
		set pixelHeight to (pageHeight * theResolution / 72) div 1
		set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(aPage's dataRepresentation()))
		set newRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
		current application's NSGraphicsContext's saveGraphicsState()
		(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep))
		(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
		current application's NSGraphicsContext's restoreGraphicsState()
		(newRep's setSize:{pageWidth, pageHeight})
		(tiffArray's addObject:newRep)
	end repeat
	set theTiffRep to current application's NSBitmapImageRep's TIFFRepresentationOfImageRepsInArray:tiffArray usingCompression:(current application's NSTIFFCompressionLZW) factor:0
	theTiffRep's writeToURL:theTIFF atomically:true
end pdfToTiff

The fact that the background color is dark is not due to errors in your code, but to AsObjC memory leaks. You can check this by converting a 2-5 page PDF. You should have generated a large TIFF with correct colors.

In short, generating a TIFF from a PDF that has dozens of pages will not work - due to memory leaks in the AsObjC code. Somewhere I saw information that at the level of Core Grachics, you can achieve better results, but there you have to use either Swift or Objective-C instead of AsObjC.

1 Like

KniazidisR. Thanks for looking at my script and for your thoughts on the matter.

In this case, I question if the cause is a memory leak for the reason detailed in my next post in this thread. Also, the script works correctly if the PDF contains images (e.g. screenshots) but not if it is text based (e.g. pages of Shane’s ASObjC book). And, the script successfully converted Shane’s 159-page ASObjC book to a TIFF on my 2023 Mac mini (although it took 29 seconds).

Just as a general aside, the Preview app has an option to employ compression when saving a PDF as a TIFF, but it accomplishes nothing on my Ventura computer. In contrast, my script does actually compress the TIFF file:

Original PDF (a screenshot of a Finder window) - 216 KB
TIFF size with script NSTIFFCompressionLZW - 1.4 MB
TIFF size with script NSTIFFCompressionNone - 56.9 MB
TIFF size with Preview with no or any compression - 56.9 MB

I think I may have identified the issue. If the PDF is text based, the background of the TIFF is either transparent or has no background. So, a simple workaround is to change the window-background color setting of Preview from gray to white.

There is a representationOfImageRepsInArray method, and one of the properties is NSImageFallbackBackgroundColor. This seemed just what I needed, but either it didn’t work or I didn’t implement it correctly.

set theTiffRep to current application's NSBitmapImageRep's representationOfImageRepsInArray:tiffArray usingType:(current application's NSBitmapImageFileTypeTIFF) |properties|:{NSImageFallbackBackgroundColor:current application's NSWhite}

Anyways, as scripts go, this one is not very useful, so I’ll leave it as is for now. Thanks again to everyone who looked at my script.

I happened upon a fix that sets the background of TIFFs that are created from text-based PDFs to white.

-- revised:2023.07.27

use framework "AppKit"
use framework "Foundation"
use framework "PDFKit"
use scripting additions

set firstPage to 1
set lastPage to 1 -- use 0 for last page of PDF
set theResolution to 300
set thePDF to POSIX path of (choose file of type {"com.adobe.pdf"})
pdfToTiff(thePDF, firstPage, lastPage, theResolution)

on pdfToTiff(thePDF, firstPage, lastPage, theResolution) -- borrows significantly from scripts by Shane Stanley
	set thePDF to current application's |NSURL|'s fileURLWithPath:thePDF
	set theDocument to (current application's PDFDocument's alloc()'s initWithURL:thePDF)
	set pdfPageCount to theDocument's pageCount()
	if lastPage > pdfPageCount then error "A page number exceeds the total number of pages in the PDF"
	if lastPage = 0 then set lastPage to pdfPageCount
	set tiffReps to current application's NSMutableArray's new()
	repeat with i from firstPage to lastPage
		set aPage to (theDocument's pageAtIndex:(i - 1))
		set pageSize to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
		set pageWidth to current application's NSWidth(pageSize)
		set pageHeight to current application's NSHeight(pageSize)
		set pixelWidth to (pageWidth * theResolution / 72) div 1
		set pixelHeight to (pageHeight * theResolution / 72) div 1
		set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(aPage's dataRepresentation()))
		set newImageRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
		current application's NSGraphicsContext's saveGraphicsState()
		(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newImageRep))
		current application's NSColor's whiteColor()'s |set|() -- set background color
		current application's NSRectFill({origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}})
		(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
		current application's NSGraphicsContext's restoreGraphicsState()
		(tiffReps's addObject:newImageRep)
	end repeat
	set tiffData to current application's NSBitmapImageRep's representationOfImageRepsInArray:tiffReps usingType:(current application's NSTIFFFileType) |properties|:{NSImageCompressionMethod:5}
	set theTIFF to thePDF's URLByDeletingPathExtension's URLByAppendingPathExtension:"tiff"
	tiffData's writeToURL:theTIFF atomically:true
end pdfToTiff
1 Like

This script is the same as that above except that it creates a separate image file for each PDF page. As written, the image files are in TIFF format but replacement lines are provided for PNG and JPG formats.

-- revised 2023.07.29

use framework "AppKit"
use framework "Foundation"
use framework "PDFKit"
use scripting additions

set firstPage to 1
set lastPage to 0 -- use 0 for last page of PDF
set theResolution to 300
set thePDF to POSIX path of (choose file of type {"com.adobe.pdf"})
pdfToImage(thePDF, firstPage, lastPage, theResolution)

on pdfToImage(thePDF, firstPage, lastPage, theResolution) -- borrows significantly from scripts by Shane Stanley
	set thePDF to current application's |NSURL|'s fileURLWithPath:thePDF
	set theDocument to (current application's PDFDocument's alloc()'s initWithURL:thePDF)
	set pdfPageCount to theDocument's pageCount()
	if lastPage > pdfPageCount then error "A page number exceeds the total number of pages in the PDF"
	if lastPage = 0 then set lastPage to pdfPageCount
	set pdfFolder to thePDF's URLByDeletingLastPathComponent
	set pdfFileName to (thePDF's URLByDeletingPathExtension())'s lastPathComponent()
	repeat with i from firstPage to lastPage
		set aPage to (theDocument's pageAtIndex:(i - 1))
		set pageSize to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
		set pageWidth to current application's NSWidth(pageSize)
		set pageHeight to current application's NSHeight(pageSize)
		set pixelWidth to (pageWidth * theResolution / 72) div 1
		set pixelHeight to (pageHeight * theResolution / 72) div 1
		set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(aPage's dataRepresentation()))
		set newImageRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
		current application's NSGraphicsContext's saveGraphicsState()
		(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newImageRep))
		current application's NSColor's whiteColor()'s |set|()
		current application's NSRectFill({origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}})
		(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
		current application's NSGraphicsContext's restoreGraphicsState()
		-- (newImageRep's setSize:{pageWidth, pageHeight}) -- if desired
		set theData to (newImageRep's representationUsingType:(current application's NSTIFFFileType) |properties|:{NSImageCompressionMethod:5}) -- 5 is LZW compression and 1 is no compression
		set imageFileName to (pdfFileName's stringByAppendingString:(" " & (i as text)))
		set theImage to ((pdfFolder's URLByAppendingPathComponent:imageFileName)'s URLByAppendingPathExtension:"tiff")
		(theData's writeToURL:theImage atomically:true)
	end repeat
end pdfToImage

-- replacement lines for PNG
-- set theData to (newImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value))
-- set theImage to ((pdfFolder's URLByAppendingPathComponent:imageFileName)'s URLByAppendingPathExtension:"png")

-- replacement lines for JPG
-- set theData to (newImageRep's representationUsingType:(current application's NSJPEGFileType) |properties|:{NSImageCompressionFactor:0.8}) -- 0.0 is maximum compresssion and 1.0 is no compression
-- set theImage to ((pdfFolder's URLByAppendingPathComponent:imageFileName)'s URLByAppendingPathExtension:"jpg")

The file sizes with one page of Shane’s ASObjC book at 300 ppi resolution were:

Tiff LZW Compression - 1.3 MB
Tiff No Compression - 33.7 MB
JPG Maximum Compression - 352 KB
JPG No Compression - 2.4 MB
PNG - 786 KB

With a large PDF, the script is 29 percent faster when creating PNG as compared with TIFF image files.

1 Like

Congrats @peavine,

I tested your script (post #5) with a PDF that has 576 pages, and I can confirm that you’ve managed to do what no one else has been able to do before - successfully convert a multi-page PDF to a multi-page TIFF in AsObjC.

My computer is of medium power class, and the script worked for almost 20 minutes, but the main thing is that it created a full-fledged TIFF. Perhaps someone will be able to increase its speed in the future.

KniazidisR. Thanks for looking at my script and for the congratulations.

FWIW, I tested my script in post 6 on my 2023 Mac mini with Shane’s 159-page ASObjC book. I tested with no and LZW compression and with 72 and 300 ppi resolution. All results are in seconds.

72 ppi no compression - 6.9
72 ppi LZW compression - 7.8
300 ppi no compression - 17.5
300 ppi LZW compression - 28.3

The image file sizes of page 2 of Shane’s book were:

72 ppi no compression - 1.9 MB
72 ppi LZW compression - 276 KB
300 ppi no compression - 33.7 MB
300 ppi LZW compression - 1.7 MB

The timing results with my script in post 5 (which creates a single multi-page TIFF) were almost identical to those above.

BTW, I modified both scripts to allow the user to specify the pages of the PDF that will be made into TIFF image files. In many use scenarios, this should effectively make the scripts much faster.

@peavine This is great! Can you recommend how to convert to PNG keeping transparency? I used to use sips and it stopped converting vector based PDFs to PNG in Ventura.

mcsprodart. I modified my script to save PDFs in PNG format, and it maintains transparency with text-based PDFs. I don’t know if that will be the case with vector-based PDFs (it appeared to work with 2 test PDFs), but it’s easy to try.

use framework "AppKit"
use framework "Foundation"
use framework "PDFKit"
use scripting additions

set firstPage to 1
set lastPage to 0 -- use 0 for last page of PDF
set theResolution to 300
set thePDF to POSIX path of (choose file of type {"com.adobe.pdf"})
pdfToPng(thePDF, firstPage, lastPage, theResolution)

on pdfToPng(thePDF, firstPage, lastPage, theResolution) -- borrows significantly from scripts by Shane Stanley
	set thePDF to current application's |NSURL|'s fileURLWithPath:thePDF
	set theDocument to (current application's PDFDocument's alloc()'s initWithURL:thePDF)
	set pdfPageCount to theDocument's pageCount()
	if lastPage > pdfPageCount then error "A page number exceeds the total number of pages in the PDF"
	if lastPage = 0 then set lastPage to pdfPageCount
	set pdfFolder to thePDF's URLByDeletingLastPathComponent
	set pdfFileName to (thePDF's URLByDeletingPathExtension())'s lastPathComponent()
	repeat with i from firstPage to lastPage
		set aPage to (theDocument's pageAtIndex:(i - 1))
		set pageSize to (aPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
		set pageWidth to current application's NSWidth(pageSize)
		set pageHeight to current application's NSHeight(pageSize)
		set pixelWidth to (pageWidth * theResolution / 72) div 1
		set pixelHeight to (pageHeight * theResolution / 72) div 1
		set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(aPage's dataRepresentation()))
		set newImageRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
		current application's NSGraphicsContext's saveGraphicsState()
		(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newImageRep))
		(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
		current application's NSGraphicsContext's restoreGraphicsState()
		-- (newImageRep's setSize:{pageWidth, pageHeight}) -- if desired
		set theData to (newImageRep's representationUsingType:(current application's NSPNGFileType) |properties|:(missing value))
		set pngFileName to (pdfFileName's stringByAppendingString:(" " & (i as text)))
		set thePNG to ((pdfFolder's URLByAppendingPathComponent:pngFileName)'s URLByAppendingPathExtension:"png")
		(theData's writeToURL:thePNG atomically:true)
	end repeat
end pdfToPng

Works like a charm! Saving PNG out of Illustrator or Acrobat was so slow and disappointing after Ventura. You saved me hours of researching and testing. Thank you!

1 Like

How do you know that the PDF has a transparent background? Or what else is transparent there?

I don’t know how you would know that with a PDF.

And there’s PDF/X with more to it:

Found out about that after my post.