Return to Digital Photography Articles

Lossless Rotation, lossless crop - How to test?

With all of the concern about lossless rotation, it's worth knowing whether or not your particular software is capable of rotation without incurring additional compression error. It is virtually impossible to do this visually, and so a more object means must be used to perform this check. Fortunately, it is very easy to do, and I describe two of these methods below.

Lossless rotation test procedure

  1. Duplicate an original JPEG image (eg. test.jpg copied to test-rotated.jpg)
  2. Open the copied version (test-rotated.jpg)
  3. Perform a 90 degree rotation and then resave it as test-rotated.jpg (if it isn't done for you automatically)
  4. Close the image
  5. Open the rotated version (test-rotated.jpg)
  6. Do three consecutive 90 degree rotations (in the same direction as the original), which should return the image back to the original orientation. Resave it again as test-rotated.jpg.
  7. Verify that the rotation between test.jpg and test-rotated.jpg is lossless using one of the described listed below:
Original vs Consecutive Rotated

Method A: Image Subtraction

The following procedure uses Photoshop, but many other graphics software packages can perform a similar operation. The idea behind this method is to get Photoshop to compute the mathematical difference in RGB values on every pixel between the original and the rotated versions. If there are any differences, use one of two methods to help make these differences easier to visualize.

  • Open up the original image (test.jpg)
  • Open up the fully-rotated image (test-rotated.jpg)
  • While holding down the shift key, drag the thumbnail of the fully-rotated image from the layer's palette to the original image. This should create a new layer in the original file's window. The shift key ensures that the new layer is centered properly above the original layer. The layer, "Background" contains test.jpg, and the layer "Layer 1" will now contain test-rotated.jpg.
  • With Layer 1 selected, change the layer mode to "Difference". The image window should go completely black as it is now displaying the mathematical difference between the two images. If there are any differences, they will probably be extremely hard to see. The next two steps will increase their visibility.
  • Under the Layer menu (or Layer Palette drop-menu), select "Merge Visible". This will cause the calculated difference between the two layers to replace the original Background layer.
  • Perform the objective comparison through either of the two visualization methods shown below (ie. either through the use of the Threshold command or the data in the Histogram window).


Visualization: Threshold

Under the Image menu, select "Threshold...". In the dialog box that comes up, enter a threshold value of 1 (the default was 128). This should cause your black difference image to either stay black or show regions of white. Any pixels that are black will represent areas that were untouched in the rotation process (ie. losslessly rotated). If any white pixels exist (or even the entire image), then the image was not rotated losslessly (ie. incremental compression error was added to the image).

Threshold difference
lossless rotation
  Threshold difference
after non-
lossless rotation

Visualization: Histogram

Alternatively, instead of using the Threshold... command, one can simply examine the histogram for the difference image. Make sure that the histogram is shown (under the Window->Histogram). Make sure that the histogram is in Expanded View not Compact View (selected by the Histogram palette drop-down menu) and that Show Statistics is also checked. If a little alert triangle is showing in the top-right of the histogram window, click on it. This causes the histogram to be calculated from every pixel in the image, rather than a small sample (the default for speed reasons).

Histogram of original image

In the window, one will see a graph that represents the number of pixels at each intensity level, from 0 (far left) to 255 (far right). As this is based on the result of the difference function, a lossless rotation should have all pixels fall into the vertical slice on the far left (ie. 0 numerical RGB differences between the before and after images). While this method is easy to spot differences quickly, it's easy to miss some pixels (as the vertical axis is highly compressed and will not show any marks if only a small number of pixels were different). The next step describes a more accurate measure to ensure accuracy in the analysis.

Lossless Rotation   Non-lossless Rotation

Below the histogram are the statistics. For the purposes of this test, the values should show a Mean of 0.00, a Std Dev of 0.00, Median of 0, and a Percentile of 100.00 when the cursor is over the first column (when Level is shown as 0). For any other cursor positions within the histogram window (ie. when Level shows a value in the range 1..255), the value for Count should be 0.

Method B: File Compare

If you really want to be sure that the two images are identical, an alternative method is to compare the JPEG filestreams. This method ensures that the actual image content is preserved identically, irrespective of any changes to the EXIF information, maker notes, timestamps, ICC profiles, thumbnails, etc.

NOTE: The file-compare will report a false negative (mismatch when they are in fact identical) in cases where one of the JPEG images has been saved with optimized huffman coding tables. Therefore, for the purposes of your test, you should ensure that optimization is turned off during the lossless rotation process. Even though the file content changes during optimization, the image content represented by the compressed datastream is the same.

The following method is complex and only really recommended for those who have an unsatiable curiosity to see how things work.

Note that one cannot simply compare file sizes as the file size can change even though a rotation was lossless. What we want to ensure is that the image data is the same, and ignore all of the other information contained within the JPEG file.

A JPEG file (as specified in the JFIF, JPEG File Interchange Format) contains a number of elements (metadata), that can include:

  • Thumbnail - smaller version of the photo for quick previews
  • EXIF Details, such as time of capture, what focal length was used, etc.
  • Comments (not often used)
  • ICC Profiles - define the color management
  • Maker Notes - specific to digital camera manufacturers; might indicate metering modes, etc.
  • Image Data - The actual compressed image

In the following, we are only interested in comparing the last item in the list: the compressed image data. A comparison is made between an original image and one that has been doubly-rotated. The doubly-rotated version can be created by performing a 90 degree clockwise rotation, resaving, closing, reopening, rotating in the opposite direction (either 90 degrees counter-clockwise or 270 degrees clockwise) and then resaving. The original and doubly-rotated version should hopefully be identical.

  1. Extract the JPEG markers in the original
    Using a utility such as jpegdump (by Kurt Stege) or JPEGsnoop (by me), locate all of the markers in the file. These JPEG markers all start with hex 0xFF. The jpegdump utility prints out each marker along with a starting offset and length.
  2. Locate the JPEG Start of Scan (SOS) for the main image in the original
    Look for the JPEG marker 0xFFDA, which represents the start of the scan (the image data). You might see two of these within a photo: one for the reduced-resolution thumbnail and the other for the main image data. By comparing the marker section lengths, it should be clear which one is the main image data. The thumbnails are generally only a few kilobytes while the main image data might be a megabyte or more. It seems that most JPEG images include the image data's Start of Scan marker at the end of the file, so try checking the end of the marker list.
  3. Record the hex offset and length of the SOS segment in the original
    Once you have located the marker for the main image data, record the hexadecimal file offset and decimal length of this marker segment.
  4. Repeat the above three steps for the doubly-rotated image
  5. Dump the hex data corresponding to the two segments
    Using a utility such as dumphex (by Robert Bachmann, download here), convert the binary bytestream from the SOS segment into a text file. Note that you can specify the starting file offset in hex and the length in decimal quantities.
  6. Using a file compare to verify equivalence between the two hex dumps
    Using a utility such as the Data Viewer within Beyond Compare (by Scooter Software), compare the two hex dumps side-by-side. Within Beyond Compare, one can configure the Data Viewer to treat the text files as column-formatted by indicating whitespace as a delimiter. This way we can later ignore the hex file offset that starts at the beginning of each line. When one sees the preview of how the delimiting works, one can select the first column (which is the hex file offset) and mark it as Removed. Once the files are then compared, the result of Files Match will be shown if the images were losslessly rotated!

Given the steps above, extracting the JPEG markers:

jpegdump.exe "e:\photos\original2.jpg"
. . . .
Marker FFDA at file offset 00005E62: (SOS) Start of Scan
field SOS has len 12
hopefully skipping 758526 bytes of compressed image data to next 0xFF.

Dumping the hex code of the SOS segment:

dumphex.exe /s05e62 /L758526 /nc /ooriginal2_hex.txt "e:\photos\original2.jpg"

The resulting hex file will appear like this:

00005E62h: FF DA 00 0C 03 01 00 02 11 03 11 00 3F 00 E5 48
00005E72h: F9 6B D8 52 3C BB 8D 1D 7D 2A 9B 27 61 7B F1 45
00005E82h: C7 7B 83 03 8A 84 C3 94 53 9C 60 D5 36 4B 41 93
00005E92h: 48 96 20 3B 79 A4 5A 62 9E D9 ED 55 B0 34 28 04
. . .

Note the Start of Scan marker (SOS) that is indicated by the hexadecimal string 0xFFDA.

In Beyond Compare, select the two hex dump text files (original2_hex.txt and original2_rot2_hex.txt) and launch Compare Using -> Data Viewer. This will prompt me to configure the Left and Right sides of the input. I select Whitespace under Delimiter and turn off First line is heading.

Setting the Beyond Compare options

Under Data Compare Rules, I then change the Column Handling for Column 1 to Removed.

Ignore the hex file offset

If the image was doubly rotated losslessly, it will report Files match at the bottom of the window.

The final compare result!


Reader's Comments:

Please leave your comments or suggestions below!

I am trying to find something that will do lossless rotations on a Mac. I have now tried 4 products and then run your test and they all failed your test. The products I tried were XnViewMP, PhotoLine, GraphicConverter and the command line tool jpegtran.

One of the problems I have is I start with a picture with orientation set to 8 in the EXIF data. I do the one rotation and these products set the orientation to 1. Then when I do the 3 rotations they don't change the orientation value again. So when I load the fully rotated file into PhotoShop its orientation is landscape but the original file is loaded as portrait.

So if I try to overlay these two images it results in a mess with lots of differences. So I tried doing 4 rotations on the second file load. This left the files in the same orientation in PS but I don't know if this invalidates the test.

According to the documentation all 4 of these products should do lossless rotations.

 Note that the Photoshop test is not the most foolproof method of confirming lossless rotation, but it is an easy starting point.

The best way would be to compare the scan segment of the two images (either with hex editor or software that can do this). If you want to send me your original image plus the versions created by the tools in question (rotate and then un-rotate to restore the original orientation), I can confirm for you whether they were lossless.
 Hi Calvin,

I just finished a blog post that I thought you might be interested in reading, I know that you have already achieved much of what I describe, but this is an automated way of performing the tasks and well I just thought you might be interested.

I found a lot of the information on your site helpful and interesting when writing my article and really think it is a top site, thanks.

 Thanks Yarrago -- you've done a great job with your utility. I have had automation of this on my to-do list for ages, so it's great to see that you've put it together. Good work!
 how do i do the following things??

1)Record the hex offset and length of the SOS segment in the original---??how will i come to know about the offset and length--??

2)Once you have located the marker for the main image data, record the hexadecimal file offset and decimal length of this marker segment----how will i come to know about the offset and length--??
 I see results similar to Travis's using both jpegtran and XnView. I get differences up to 3 in each RGB color channel for rotations of 90, 180, and 270 degrees. There are no differences when rotated 360 degrees.

I'm not a JPEG expert, but shouldn't the lossless rotation result in identical (but rotated) YCC values for each pixel? If so, shouldn't the equations from YCC -> RGB produce identical results regardless of the rotation?

I used both GimPhoto 1.4.3 and Paint.NET 3.5.1 to load the JPGs and perform the differencing. They both produced identical difference images for each rotation. Are there any image editors known to use a high-quality (e.g. floating-point) YCC -> RGB conversion? This would allow one to verify if the color space conversion is truly the culprit.
 Interesting observation. My first test with Photoshop didn't show this, but I will explore further to see if the subsampling may be part of what you're seeing. I will update once I've had some time to look into it. Thanks!
 Why doesn't lossless rotation work for 90º and -90º ?

Here I got a jpeg of 3072 x 2048 pixels, that's (192 x 16) x (128 x 16) so it fits the MCU boundaries perfectly.
I examined the RGB value of a pixel somewhere at an easy locatable spot in the picture. The eyedropper in PaintShopPro10 shows a RGB values of 247,208,201.
After 90º CW "lossless" rotation it yields however 244,209,203.
After another 90º CW "lossless" rotation (i.e. 180º total) the values are restored to 247,208,201!
Carrying on with a third 90º CW "lossless" rotation (270º total) the values become 244,209,203 again, but
after the final 4th 90º CW "lossless" rotation (360º total) the RGB returns to 244,209,203...

Can't the "lossless" rotation algorithms preserve the color values for +90º and -90º? Why are the RGB values correct for 180º and 360º?

Not all lossless algorithms behave likewise. BetterJPEG and IrfanView gave slightly different RGB values for this pixel when the picture was rotated to 90º and -90º.
 Hi Travis -- What you are observing is probably a side-effect of the color-conversion process that is used to translate the YCC "color" values into RGB color values. This conversion is often quite imprecise and using integer math it can lead to rounding errors.

In all likelihood the rotation with both programs is indeed lossless (and could be verified as such in the YCC domain), but what you see in the image editor (ie. the RGB value) may differ slightly, depending on how it was calculated / rounded. Have a look at my page of JPEG Color Conversion Error for more details. Hope that helps!
2009-02-11Nikolay Assa
 Great explanation, thanks!
Another test to (dis)prove lossless rotation:
save test.jpg as bmp and test-rotated.jpg as bmp (using MS Paint or any viewer that can save as bmp) and then binary compare the resulting two bmp files.
To my great surprise, Windows Picture and Fax Viewer (the default image viewer in Windows XP) did lossless rotation!
(Indeed the file size changed, but the resulting bmp files were identical)
 Thanks Niko -- while saving out as BMP and performing a binary compare may work, binary comparisons will generally lead to "false negatives" suggesting that the files are not equivalent when in fact they are.

The usual problem with binary compares is that the metadata (non-image data), that is also stored in the file, may be modified in a some way (eg. timestamps) leading to differences. Furthermore, if the JPEG decoder / bitmap encoder had any variability in the process (inconsistent rounding results, color conversion, etc.), you could have mismatches come up there.

That said, the BMP format does not have much in the way of accompanying data that is likely to vary between resaves of the same file. Therefore, it is possible that your method may indeed help give you an indication of "yes, these are losslessly matched", but you won't be able to draw any conclusions if they don't match (ie. a non-match does not mean that a conversion was not lossless).
 Hi Calvin,
Thanks for the explanation.
I have a comment about method A.
I tried and I didn't get the results I expected.
An image rotated with a lossless-rotation software gave me a "bad result" (loss rotation, lots of white); I tried to understand why.
Maybe I found the reason and I would like you to confirm or not.
Original image 2890x1921 pixel (both not multiple of 16).
After rotation with different programs, the rotated image had different size:
2890x1921 Windows Picture Viewer
2888x1920 JPEG Rotator
2888x1920 Better JPEG
2890x1921 Photoshop CS3
I understand the light crop is made for lossless rotation (to have multiple of 16 or 8).
Applying your method, when I create a new layer in Photoshop holding the shift key I get the two images centered... but this, in my opinion, doesn't mean the corresponding pixels are centered (due to the few pixels crop) for the two images have different size.
They are perfectly centered in the images of the same size after rotation (Windows Viewer and Photoshop), they are not with the others.
If the pixels "don't match" I get an unreliable final result.
I tried then to move the copied layer of a few pixels using the arrow keys (at maximum zoom) to let them matching exactly; in this case I got the black image I expected to prove the lossless rotation.
Please tell me what you think about and if there is another way to center the pixels of images of different size.
 Yes, in order to use the Photoshop test you would specifically need to align the pixels to overlap. Because the crop typically only happens from one side, you are not guaranteed to have a centered image line up properly. The Photoshop method is not a great test for lossless rotation, but if you still want to use this technique and you are faced with a cropped situation: change the Opacity of the top-most layer to ~ 50% and then zoom in to 100% view. By using the arrow keys you should be able to align the images correctly. Make sure you revert the opacity back to 100% before you do the difference test.
 As an engineer, I appreciate the technical detail and methodology you've demonstrated. However, as a photographer, I find the resultant image from "Threshold difference after non-lossless rotation" to be pretty damn cool.

There's an artistic effect there. I've seen worse effects done on purpose. :-)

- Arved
 Agreed -- lots of potential :)
 Great page. I'm looking for a way to check whether an image ash been rotated, and by how much. Basically I need a program that can sweep through a few thousand JPG files and spit out the rotation parameter off those that had been (losslessly) rotated.

Background: I went through some 3000 scaled-down photos and rotated them manually. Now I want to apply the rotation also to the originals. The EXIF tags will indicate which
images were rotated (example ) below, but not which direction.
(+90 or -90).

CIMG1060.800x600.JPG Exif Resolution : 3072 x 2304
CIMG1061.800x600.JPG Exif Resolution : 2304 x 3072

I suppose I could narrow down the task of manual rotation by looking only at images I know have been rotated, but extracting the rotation would be even better,
2007-08-17Kenn Goutal
 Hi! I came across this page (.../photo/lossless-rotation-test.html) while looking for info re "lossless JPEG rotation".
But the first thing I noticed was how beautiful it is!
Anwway ... onward!
You may also be right about color space conversion being one of the cause for the fringe blocks to open differently in Photoshop. This can happen if the color space conversion involves dithering. Dithering would require considering colors of neighbouring pixels. Dithering seems to be optional for colorspace conversion in Photoshop but it seems this option only applies to explicit conversions and has no effect on JPEG colorspace conversions
What makes me think of interblock smoothing in Photoshop is that in the crop-difference test the difference only seems to appear in the fringe blocks. The difference also appears in unedited blocks adjucent to edited blocks ("lossless" text stamp or red-eye edit in BetterJPEG).

This leads me to believe that result of decoding of a particular block in Photoshop depends on information in adjucent blocks.

In any case, if I open both the original and the cropped image with the BetterJPEG plugin for Photoshop instead of opening them with the Photoshop Open command, the difference test results in a 100% black rectangle, with no excuses. So I agree with you in that the Photoshop's built-in Open command is not the best way to open files to test for lossless crop.

Thank you for your site, it is nice to see someone exploring and explaining internals of digital photography.
 Yes... I'm definitely going to have to re-examine the images again to see why the fringe blocks are exhibiting the differences. Your reasoning does sound very plausible. Thanks for the insight, Alex!
Just a small comment.
Testing for lossless crop with Photoshop gives a pretty good result - when you look at the difference the whole rectangle is perfectly black except for few blocks directly adjacent to the cropped image edge.

It seems the reason for the difference in the edge blocks is that Photoshop performs smoothing between MCU/DCT blocks when opening JPEG images to reduce JPEG blocking look. The result after smoothing and color conversion is the same for all corresponding blocks in the original and the cropped image except for the edge blocks, since in the cropped image they have no neighbouring blocks at one or two sides and so the smoothing gives a different result.
 Thanks Alex -- I did observe a very slight difference in some of the fringe blocks, but I'm not convinced that I see smoothing for deblocking in Photoshop. For example, have a look at the following highly-compressed example after rendering in Photoshop (left) vs original (right):

The other reason why I don't think Photoshop would do this is because: the editor works in the RGB bitmap domain, and the deblocking would be done as it decompressed and converted from YCC to RGB. By adding in a deblocking step, it would mean that a resave would always incur additive image degeneration due to the deblocking.

Using Photoshop to draw a comparison of details in the JPEG domain (instead of uncompressed RGB) is certainly not the most precise method, but as most people don't have tools to work in the JPEG domain, it is an OK starting point. A real lossless crop, as produced by tools such as BetterJpeg will result in vastly different results shown in this comparison than if Photoshop or other image editor were used for the crop.

That said, when I get time I'm going to try to analyze this further to try to get a better handle on why differences may still exist.
 Can you describe the method for testing Lossless Crop? My immediate impression at the top of the page was that you can do a test for Lossless Crop also, but you describe the Test for Lossless Rotation in detail on this page. I have been able to use this procedure (with "Threshold" method on PS Elements 5.0) to verify that PS does not do lossless rotate and BetterJpeg does do lossless rotation. However, when I just tried to mirror the process above for testing "rotate" in order to test "crop", I end up with an outcome that would indicate that Better Jpeg is NOT losslessly cropping, yet they advertise as such and it would appear that if they are using algorithims to deliver lossless rotate then they woudl be doing the same for lossless crop. I would like to make sure I am testing correctly for "crop" and wanted to know if the Threshold method above is still valide for doing so. If so, then I presume it is really difficult for any algorithim to deliver true lossless crop and must settle for "near" lossless when cropping or less "lossy"?
 Hi Dave --

Testing for Lossless Crop is actually a fair bit more involved than for Lossless Rotation. The main reason for this is that lossless crop is not a reversible operation, while rotation is. Therefore, with crop, you can't compare against the original without using another utility. As there are very few programs that will do this accurately, one ends up relying on programs like Photoshop to get a reasonable comparison. Unfortunately, Photoshop must perform color space conversions, which always introduce some error into your comparison.

Nonetheless, you can still do a similar Photoshop-based test:
  • Perform the crop using the utility of your choice.
    For example, in jpegtran, I can trim off the rightmost and bottom 16 pixels of a 3072x2048 image with the following command: jpegtran -crop 3056x2032+0+0 original.jpg output_crop.jpg
  • Open the original file in Photoshop
  • Open the cropped version in Photoshop
  • With the Move tool, drag the thumbnail of the Background layer in the Layers Palette of the cropped image onto the main window of the original image. This will copy the cropped image into a new layer in the original image. Close the cropped image file.
  • In the remaining file, you'll have two layers: the Background layer will be the original, while Layer 1 will be the cropped version. The default layer copy process should have placed layer 1's top-left corner at the top-left corner of the Background layer, which will mean that the missing 16-pixel wide strip will be on the right side and bottom sides
  • Change Layer 1's layer mode to Difference. Most of the image will be black except for the strip on the right and bottom.
  • Now, we need to use Photoshop's cropping method, which will be lossless in the bitmap domain (not JPEG domain!)
  • Go to Canvas Size and make sure that the top-left corner is selected in the Anchor mode. Make sure Relative is not checked.
  • Enter in the post-crop dimensions (in this case, 3056x2032) and click OK. When the dialog box comes up warning you "The new canvas size is smaller than the current canvas size; some clipping will occur", click Proceed.
  • Now we want to flatten the image, so we Select All, Copy Merged, create a New Layer and then Paste. This creates Layer 2.
  • Finally, to aide in your visualization of the differences, select Auto Levels
  • In the Histogram palette, make sure Selected Layer is shown. What you want to look for is the Mean value (or Count value at levels other than 0).
Ideally, one would see a Mean value of 0.0 if the crop was performed losslessly, and a non-zero value otherwise. Unfortunately, because we're using photoshop, there are a few potential sources of error in this comparison, but it is a reasonable starting point.

NOTE: By dragging the layer from the Layers pallette onto the drawing window of the other file, Photoshop aligns the copied layer automatically with the top-left corner. Alignment is important for these sort of Layer Difference tests to work correctly.
 It seems that comparing image data after the SOS marker isn't enough although it works most of the time. Is it possible that other sections like the Quantization Table is changed such that the resulting image is different while the remaining parts are intact?
 Franky -- you're correct in that a change in the DQT would make the comparison of SOS not a guarantee of equivalence. However, for the DQT to be different, the JPEG encoder most certainly will create a completely different SOS (scan data) region. I believe that the scenario whereby two images are encoded with the same resulting SOS but different DQT is virtually impossible -- assuming a normal JPEG encoder / software (that isn't messing around with the DQT after the fact!). This is because the DQT is always defined before the scan data is calculated.
 I would like to hear your thoughts on a way I found to test lossless rotation using iMatch. The way I did it:

  1. Copy an original JPEG image
  2. Rotate the copied image using the tool you wish to test lossless rotation.
  3. Rotate the copied image back to the original orientation
  4. Make sure iMatch is configured to NOT auto-rotate images when loaded into its database
  5. Use the iMatch database wizard to load the original and copied/rotated images into the iMatch database.
  6. Open both images into the iMatch image editor.
  7. Use the Image Mathematical tool to subtract the copied/rotated image from the original image to create a third image, which should be completely black if the rotation is lossless.
  8. To confirm that the image is lossless, use the Colors - Count Colors tool. The color count should be one.

I like this method since it's fairly easy and uses a tool I already own. Of course, this method could be used in any other image editor that supports the two capabilities of mathematically subtracting one image from another image and counting the number of colors in an image.

Thank you for your great website.
 Andrew -- your methodology sounds perfectly fine, so long as one relies on the Count Colors and not just an assessment of the difference image being visibly-black. Thanks for the option!

You can do this check using the free, Paint.NET program, available here:

The way I did it:

  1. open the copied, rotated file.
  2. Edit...Select All
  3. Edit...Copy
  4. File...Open (..then specify the original file)
  5. Edit...Paste into New layer
  6. Layers...Layer Properties...Difference
  7. Image...Flatten
  8. Layers...Adjustments...Levels....Auto

This will show you the difference between the two images.

The Paint.NET program is also useful for manipulating images in all sorts of other ways. It's a general purpose image editor.


Thanks! Good to see a decent, free open-source alternative to Photoshop.


Leave a comment or suggestion for this page:

(Never Shown - Optional)