Sips VS mdls

I’m puzzled.
Usually my preferred method to extract measurements of pictures (width and hight) was to invoke sips.

For some time now, I didn’t see, but only sporadic, that the width and hight values weren’t in the right sequence, hight was measured as width, width measured as hight. Maybe because sips was reading data from the picture’s original orientation shot with the camera ?

I don’t know, as most of the pictures in Finder have the right hight and width, interrogating sips.
Only some picture files in sips have exchanged measures.
Now, I tried another approach and used mdls instead.
Result : sips was wrong and mdls right.
Finally, I’d like to know if the fault is mine or if mdls is the better way to extract info? Thanks for any suggestion

tell application "Finder" to set sel to item 1 of (get the selection) as text

#set {H, W} to {word 2 of item 1, word 2 of item 2} of paragraphs of (do shell script "sips -g pixelHeight -g pixelWidth '" & POSIX path of sel & "' | tail -2")

set {H, W} to {word -1 of item 1, word -1 of item 2} of paragraphs of (do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth " & POSIX path of sel)
log {H, W}

Some output I get :

tell current application
	do shell script "sips -g pixelHeight -g pixelWidth '/Users/home/Pictures/IMG_6999.jpg' | tail -2"
		--> "  pixelHeight: 3456\r  pixelWidth: 5184"
	(*3456, 5184*)
	do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth /Users/home/Pictures/IMG_6999.jpg"
		--> "kMDItemPixelHeight = 5184\rkMDItemPixelWidth  = 3456"
	(*5184, 3456*)
end tell

Just look at that.
I selected two versions of the same picture

tell application "Finder" to set {img1, img2} to items 1 thru 2 of (get the selection as alias list)

set infos1 to (do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth -name kMDItemOrientation " & quoted form of POSIX path of img1)
set infos2 to (do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth -name kMDItemOrientation " & quoted form of POSIX path of img2)

the log history was :

tell application "Finder"
	get selection
		--> {alias "SSD 1000:Users:**********:Desktop:images:DSC_0257 - rotated.jpg", alias "SSD 1000:Users:**********:Desktop:images:DSC_0257.JPG"}
end tell
tell current application
	do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth -name kMDItemOrientation '/Users/**********/Desktop/images/DSC_0257 - rotated.jpg'"
		--> "kMDItemOrientation = 0
kMDItemPixelHeight = 1746
kMDItemPixelWidth  = 3104"
	do shell script "mdls -name kMDItemPixelHeight -name kMDItemPixelWidth -name kMDItemOrientation '/Users/**********/Desktop/images/DSC_0257.JPG'"
		--> "kMDItemOrientation = 1
kMDItemPixelHeight = 3104
kMDItemPixelWidth  = 1746"
end tell

Which is what would be returned by sips.

On my side I would use none of them but:

use AppleScript version "2.5" -- 10.11 or later
use framework "Foundation"
use scripting additions

on grabThreeInfos:aFile
	set theNSURL to current application's NSURL's fileURLWithPath:(POSIX path of aFile)
	set nsMetaItem to current application's NSMetadataItem's alloc()'s initWithURL:theNSURL
	# Build a list of every available metadatas
	set theMetadata to nsMetaItem's valuesForAttributes:(nsMetaItem's attributes())
	# Duplicate the dictionary in a mutable one from which we may remove items.
	set theKey to "kMDItemPixelCount"
	return {(nsMetaItem's valueForAttribute:"kMDItemOrientation") as string, (nsMetaItem's valueForAttribute:"kMDItemPixelHeight") as string, (nsMetaItem's valueForAttribute:"kMDItemPixelWidth") as string}
end grabThreeInfos:

tell application "Finder" to set {img1, img2} to items 1 thru 2 of (get the selection as alias list)
set infos1 to my grabThreeInfos:img1
log result (*0, 1746, 3104*)
set infos2 to my grabThreeInfos:img2
log result (*1, 3104, 1746*)

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 5 mai 2020 17:56:07

Joy. I don’t have an answer to your question but thought I would add some data FWIW.

I found a digital photo which I had taken in portrait mode and which showed different width/height results with Sips and Mdls. The pertinent Exif data obtained with exiftool was:

Exif Image Width 3072
Exif Image Height 2304
Orientation: Rotate 90 CW

Preview, Sips, and Image Events showed the image width as 3072 but Mdls showed the image width as 2304. I also tried Yvan’s second script and it returned the following for the test image. It appears that 3072 is the reported height.

(1, 3072, 2304)

I don’t know if MDLS is a better data source, but it certainly doesn’t appear you are doing anything wrong.

@Yvan
Great examples, I shall give a try to your Objc code.
And yeah, I omitted kMDItemOrientation because the Metadata was always set on 1 ( rotated), be it for files with or without the right measurements, very odd…

@peavine
Yep, so I’m not an isolated case :wink:
The Finder must read picture’s measurements from the Metadata, because height and width are always correct. Which isn’t the case if I’m using sips

I made complementary tests with mdls.

It makes no difference between a picture rotated 90° left or 90° right
It makes no difference too between an original picture and the same rotated 180°

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 5 mai 2020 19:52:54

This could not be tested. It is enough to realize that a rectangle rotated 90 degrees clockwise or counterclockwise should have the same height x width. You didn’t check the image rotated 360 degrees. I am sure that in your extensive log-record you will find the original rectangle. Also, it would be nice to test from 720 degrees, etc.

It would be better to test the image by deleting the exif data, rotating it 90 degrees (enough in one direction).

I just wanted to see if the tool was making the difference between rotation 90° left and 90° right which makes a serious difference about what we see, same interrogation between a rotation 0° and a rotation 180° which makes a real difference too.
Is it really useful to add that I know that rotation 0° and rotation 360° give the same result ?

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mardi 5 mai 2020 20:42:59

This script uses exiftool and returns a list containing image width and height based on the value of the orientation exif tag.

set landscapeOrientation to {"Horizontal (normal)", "Rotate 180"}
set portraitOrientation to {"Rotate 90 CW", "Rotate 270 CW"}

tell application "Finder" to set theFile to selection as alias
set theFile to quoted form of POSIX path of theFile

do shell script "/usr/local/bin/exiftool -s3 -ImageWidth -ImageHeight -orientation " & theFile
set imageData to paragraphs of result

if item 3 of imageData is in landscapeOrientation then
	set {imageWidth, ImageHeight} to {item 1, item 2} of imageData
else if item 3 of imageData is in portraitOrientation then
	set {imageWidth, ImageHeight} to {item 2, item 1} of imageData
end if

BTW, I was curious if there are any standards as to the Exif orientation tags and found that there are:

https://exiftool.org/TagNames/EXIF.html

Width and height mean different things in different contexts.

Orientation is a purely metadata concept, designed so photos can automatically be rotated when opened. It makes sense that Spotlight metadata reflects the orientation in its values, given its use for things like Finder info – user-facing values. It’s possible the EXIF result reflects its use in the days before orientation metadata, and is avoiding potential ambiguity.

In the case of sips, the values are used for clipping, scaling, etc, and they therefore need to reflect the pixel values in the actual image data of the file, ignoring any metadata. When sips opens a file, it doesn’t consult the metadata for orientation (or anything else).

So which tool you use should depend on what you’re using the values for.

Here’s an alternative that returns values similar to sips.

use AppleScript version "2.5"
use scripting additions
use framework "Foundation"
use framework "AppKit"

set theFile to posix path of (choose file)
set imageRep to current application's NSBitmapImageRep's imageRepWithContentsOfFile:theFile
set theWidth to imageRep's pixelsWide()
set theHeight to imageRep's pixelsHigh()

Shane.Do you know where MDLS obtains the portrait/landscape data from? How does macOS know if I held my phone in landscape or portrait mode when I took a photo. Thanks.

The phone adds orientation metadata to the image. It’s in the exif data, and it may well be in some Apple-specific metadata. The actual arrangement of pixels in terms of across-by-down is always landscape, though.

Thanks Shane. What you say makes sense, because I couldn’t find a single digital photo in which the exif pixel height was greater than the pixel width.

I looked at every exif tag for my Pixel phone and the only orientation-related tag was named “orientation”. So, in this sense, the issue appears fairly simple.

Thanks for that peavine, I was trying to see if the rotation returned in metadatas was giving such detailed information.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mercredi 6 mai 2020 11:46:54

There are two differences between your code and Shane’s one

(1) you define a property :

property |⌘| : a reference to current application

alloying you to use below a concise syntax:

set imageRep to |⌘|'s NSImageRep's imageRepWithContentsOfFile:theFile

although Shane use the standard spelling

set imageRep to current application's NSBitmapImageRep's imageRepWithContentsOfFile:theFile

In fact you don’t call the same Class: NSImageRep versus NSBitmapImageRep but I’m not sure that I really understand the difference between them (if it really exists).

The use of the property may be interesting when a script make numerous use of current application.
When there is a single occurence, it requires more typing than the standard spelling.
More, when there are numerous occurences, the short syntax may be easier to read.

It’s mostly interesting in a variation:

property |NSURL| : a reference to current application's NSURL
property NSArray : a reference to current application's NSArray

alloying the concise syntax:

|NSURL|'s fileURLWithPath:(POSIX path of theFileOrPathInput)
set anArray to (NSArray's arrayWithObject:aList

which is more readable than the verbose:

current application's NSURL's fileURLWithPath:(POSIX path of theFileOrPathInput)
set anArray to (current application's NSArray's arrayWithObject:aList

mostly when such ones appears quite often
but clearly it’s mostly matter of taste, except when for some unknown reason, instructions perfectly correct refuse to compile as I met some days ago.
The problem disappeared after copying the entire script an pasting into a new blank one.
Seems simple days after but frustrating as long as the copy/paste scheme doesn’t come to mind.

(2) You added an extraneous instruction gathering the two grabbed values in a single object :

set result to {theWidthFile, theHeightFile}

All in all both versions are technically identical.

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) mercredi 6 mai 2020 21:37:14

NSBitmapImageRep is a subclass of NSImageRep, so it inherits all the methods of its superclass.

The basic difference between our scripts is that if you choose a file that doesn’t contain a bitmap image — say a PDF — my script will throw an error saying it can’t make an NSBitmapImageRep from it, whereas Fredrik’s will succeed on that line but fail when he tries to get the number of pixels, because he has an NSPDFImageRep, which has no pixelsWide and pixelHigh methods.

The size of an NSImage is in points, not pixels. If you only need the pixels, there’s no need to go via anNSImage.

Isn’t it what I wrote as:

Yvan KOENIG running High Sierra 10.13.6 in French (VALLAURIS, France) jeudi 7 mai 2020 16:06:50