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.
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
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.
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!
@peavine or anyone else… I have been using your code great but someone pointed out that it was antialiasing and looked blurry. I updated to add a few lines of code and it errors or has no effect on the antialiasing:
`on pdfToPng(thePDF, firstPage, lastPage, theResolution) – Thanks to Peavine on macscripter. 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))
(theImageRep's setShouldAntialias:false) -- change to suit ----added to pavines code
(theImageRep's setImageInterpolation:(current application's NSImageInterpolationNone)) ----added to peavine's code
current application's NSGraphicsContext's restoreGraphicsState()
(current application's newImageRep's setSize:{pageWidth, pageHeight}) -- if desired
set theData to (newImageRep's representationUsingType:(current application's NSBitmapImageFileTypePNG) |properties|:(missing value))
set pngFileName to (pdfFileName) as string
set thePNG to ((pdfFolder's URLByAppendingPathComponent:pngFileName)'s URLByAppendingPathExtension:"png")
--log thePNG
(theData's writeToURL:thePNG atomically:true)
end repeat
mcsprodart. I’ll be happy to look at the AppleScript, but I have a question first. The shortcut included below converts single- and multi-page, image- and document-based PDFs to PNG files. In my testing, the quality of the PNG files is quite good, and the execution speed is acceptable with all but the largest of PDFs. The PNG files are saved in the same folder as the PDF files, but you can add a subpath.
If the shortcut won’t do the job, let me know, and I’ll look at the AppleScript.
I can’t use a shortcut. It is part of a larger script that saves out files for print purposes. My issue is I either have to use ghostcript to do it or I want to learn how to do it in ASobjC. ASobjC is more predictable as long as it doesn’t antialias. Thank you though for the quick answer. My confusion starts with all of the references to NSGraphicsContext’s or theImageRep’s and restoreGraphicsState and ends with the two lines
(theImageRep’s setShouldAntialias:false) – change to suit ----added to pavines code
(theImageRep’s setImageInterpolation:(current application’s NSImageInterpolationNone))
mcsprodart. My shortcut suggestion was not responsive to the issue you raised. Sorry about that. Perhaps it will be of use to another forum member somewhere down the road.
Unfortunately, I do not have sufficient knowledge in this area to provide a really knowledgeable response to the question you raise. The script included below sets the shouldAntialias and imageInterpolation (constants here) properties, and it compiles and runs, but it would take Shane or someone with similar skills to confirm that the syntax is correct.
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"})
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()
set theContext to (current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newImageRep)
(current application's NSGraphicsContext's setCurrentContext:theContext)
(theContext's setShouldAntialias:false) -- change to suit
(theContext's setImageInterpolation:(current application's NSImageInterpolationHigh)) -- change to suit
(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()
-- (current application's newImageRep's setSize:{pageWidth, pageHeight}) -- if desired
set theData to (newImageRep's representationUsingType:(current application's NSBitmapImageFileTypePNG) |properties|:(missing value))
set thePNG to ((pdfFolder's URLByAppendingPathComponent:pdfFileName)'s URLByAppendingPathExtension:"png")
(theData's writeToURL:thePNG atomically:true)
end repeat
end pdfToPng
@peavine thank you @Shane_Stanley or any other ASobjc experts here… can you help me understand the references above to the variable names and if I am correctly addressing them? Also what is the purpose of restoregraphicsstate? Is that why the antialiasing and interpolation have no effect?