Advice on working with pixel values in files

tl;dr: I’m looking for advice on is loading the pixel values into Applescript and writing pixel values out to file.

More info:

I’m just looking for advice here before getting started on doing something… a project I’m sure Applescript is the wrong language for. But when all you’ve got is a hammer…

I just need something working so I can 1. Experiment and tweak the math, and 2. use as a proof-of-concept. If it works, then we can hire a programmer to do the thing in C++ as a Photoshop Plugin or something.

I need to do math on pixel values. It’s too much math and too different from anything Photoshop does natively for me to just script it with Photoshop doing it.

So what I’m looking for advice on is loading the pixel values into Applescript and writing pixel values out to file.

This will eventually need to run on files around 12" x 14" at 300 dpi. So I’m pretty sure that if I move the “Color Sampler Tool” around the 15 million pixels of my document to get the values, then build selection and fill them with colors one by one to produce the output colors after my calculations, that I’m going to be looking at run times that aren’t even OK for a proof on concept… like, weeks. I probably need to spend a while tweaking my math, I need to at least have the thing finish a file in a few hours to make a usable feedback loop for development. If course, I’ll test on smaller files, but I still need about 1.5 megapixels just to see what’s going on.

So the questions is, what’s the best way for me to get a bunch (all) of the pixel values from a file into Applescript, and then back out to a file?

I’ve been looking at this thread

specifically, Dominic’s post using tiffutil and hexdump to get at the values. All my calculations will be done in LAB color, so if it’s possible to save in a format that support LAB color and write back out in it, I expect that will speed things up versus converting to and back from RGB in Applescript.

Any advice? Has anything changed in the 10 years since that thread I found? I thought I’d ask before I start making stupid mistakes.

Thanks in advance,


Yep. I reckon you’d be better to hire someone to write a simple Objective-C framework you can call from AppleScript to test it out. The LAB conversion complicates the maths a bit, but in terms of proof-of-concept, it’s not much code.

FWIW, here’s some ASObjC that does something similar. In this case it’s just twiddling RGBA values. And for a 20px x 20px image it takes nearly 3 seconds, so it would be more than a day for your images. Don’t choose anything but a very small image! But if written in Objective-C with some tweaks you can’t do in ASObjC, it should be a suitable test bed.

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use framework "Quartz"
use scripting additions

set theFile to POSIX path of (choose file)
-- get image
set theImage to current application's NSImage's alloc()'s initWithContentsOfFile:theFile
set theSize to theImage's |size|()
set oldBitMap to theImage's representations()'s firstObject()
set theW to oldBitMap's pixelsWide()
set theH to oldBitMap's pixelsHigh()
--make new bitmap
set bmRep to current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:theW pixelsHigh:theH bitsPerSample:8 samplesPerPixel:4 hasAlpha:true isPlanar:false colorSpaceName:(current application's NSCalibratedRGBColorSpace) bytesPerRow:(theW * 4) bitsPerPixel:0
-- draw image into bitmap
current application's NSGraphicsContext's saveGraphicsState()
set theContext to current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:bmRep
current application's NSGraphicsContext's setCurrentContext:theContext
theImage's drawAtPoint:{0, 0} fromRect:{{0, 0}, theSize} operation:(current application's NSCompositeCopy) fraction:1.0
theContext's flushGraphics()
current application's NSGraphicsContext's restoreGraphicsState()
-- fiddle with bits
repeat with i from 0 to theW - 1
	repeat with j from 0 to theH - 1
		-- in ASObjC we have to get colors, and get components separately
		set theColor to (bmRep's colorAtX:i y:j)
		set theRed to theColor's redComponent()
		set theGreen to theColor's greenComponent()
		set theBlue to theColor's blueComponent()
		set theAlpha to theColor's alphaComponent()
		-- make new color from components
		set theColor to (current application's NSColor's colorWithDeviceRed:theRed green:theRed blue:theRed alpha:theAlpha)
		-- replace value at pixel
		(bmRep's setColor:theColor atX:i y:j)
	end repeat
end repeat
-- save as new tif
set theData to (bmRep's representationUsingType:(current application's NSTIFFFileType) |properties|:{NSTIFFCompression:(current application's NSTIFFCompressionLZW)})
set theResult to (theData's writeToFile:(theFile & "-new.tif") atomically:true)

Edited to get pixel values directly.

As always, thanks so much, Shane. You whip up amazing things.

I’ll test the run time on a few images and get an idea of how the time scales with image size. If it doesn’t do better than linear time, I probably can’t do samples big enough to get much of an idea how it’s working… but maybe.

After posting, I messed around with saving in various image formats and opening the files in a text editor and a hex editor, and looked up the documentation on DCS EPS, PSD, and TIFF (because they support LAB).

It does look like a mess. I expect that if I figured it out, that reading the pixel values as LAB colors straight out of a file, doing math, and writing them back would be fastest, but it looks like a huge pain. Ruby has PSD.rb for dealing with PSD’s directly, that would probably be a fast way to mock this up, if I knew Ruby.

Unfortunately, I don’t think any developer time is going to be funded if I can’t first show that I can get the math right and get some results, so it’s a bit of a Catch 22. I can’t work on it without developer help, and I can’t get developer help without showing it works.

I’ll mess around with your code and see what I can get - it may be fast enough to let me do what I need.

Once again, many thanks.

  • Tom.

What image format is this expecting… does it matter? I tried it with a PNG, TIFF, and BMP. Without any code to change pixel values. I’d assumed TIFF because it resaves as a TIFF. On a 9-pixel image, the program ran for minutes on the TIFF until I stopped it. I tried a dialog in the repeat loop, and it is repeating, but way more than 9 times. The PNG and BMP image it ran on, but it’s counting 36 pixels instead of 9. And it should be making the “-new.tiff” file identical to the original now, but here’s a (zoomed-in) screenshot of the original on the left and the result on the right:

Underneath the alpha channel on all the new pixels, they’re all black.

I don’t fully understand this line:

set bps to theImage’s representations()'s firstObject()'s bitsPerSample()

But on a hunch I switched


Which makes it count 9 pixels and generate a new file that’s 9 pixels, but the new file is still in greyscale.

I feel kind of worthless with the troubleshooting here, I know very little ASObjC, and the plain Applescript parts look fine to me.

Maybe it’s just a file format difference… I can save in any format the script wants, maybe it’s just a matter of knowing what that is. If it’s helpful, I put the test file I was using here:
/Volumes/Hackintosh HD/Users/work/Dropbox/Public/PS RGB 9 color test.png

  • Tom.

It should work with any bitmap format Preview can read.

I included some last-minute code to convert between points and pixels that was a bad idea. Try the updated version.

That was the bad idea. See the replacement for it.

That’s what the sample code does – it’s taking the green component and using that value for red, green and blue – that’s effectively greyscale. It was just something random so you could see something happening.

As always, thanks. That was silly of me to miss that you’re using “theRed” for every value when you convert back to ObjC to commit the color value edits. I didn’t really look at that because I wrongly assumed that whatever was changing the size of the image was also causing the B&W conversion, and had wrongly assumed the default state was to write the existing values back out to the new file.

It’s late here now, will have to play with it in the morning.

  • Tom.