Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

jpegtran optimized file size regression between mozjpeg and 3.1 and 4.1.1 #433

Open
pts opened this issue Mar 14, 2023 · 12 comments
Open

jpegtran optimized file size regression between mozjpeg and 3.1 and 4.1.1 #433

pts opened this issue Mar 14, 2023 · 12 comments

Comments

@pts
Copy link

pts commented Mar 14, 2023

mozjpegtran in mozjpeg 4.1.1 (same output as 4.0.3) creates larger output files than the output files in mozjpeg 3.1 (same output as 4.0.0, 3.3.1 and 3.0) for some inputs (e.g. lab.jpg). For some other input files (e.g. lenna.jpg), it's the other way round.

Examples:

$ jpegtran-mozjpeg-3.1 -copy none <lab.jpg >lab31.jpg
$ jpegtran-mozjpeg-4.1.1 -copy none <lab.jpg >lab411.jpg  # Larger than lab31.jpg.
$ jpegtran-mozjpeg-3.1 -copy none <lenna.jpg >lenna31.jpg
$ jpegtran-mozjpeg-4.1.1 -copy none <lenna.jpg >lenna411.jpg  # Smaller than lenna31.jpg.

Input files:

Output files:

  • lab31.jpg: 400769 bytes, identical file created by version 3.0, 3.1, 3.3.1 and 4.0.0
  • lab411.jpg: 402438 bytes, identical file created by version 4.0.3 and 4.1.1
  • lenna31.jpg: 36172 bytes, identical file created by version 3.0, 3.1, 3.3.1 and 4.0.0
  • lenna411.jpg: 36111 bytes, identical file created by version 4.0.3 and 4.1.1

Output of the CLI version of jpegsnoop on various files:

  • lab.jpegsnoop.out
  • lab31.jpegsnoop.out
  • lab411.jpegsnoop.out
  • Please note that I don't trust jpegsnoop much, because all 3 outputs contain an error ERROR: Early EOF - file may be missing EOI, but all other image processing software I tried can read, display and process these JPEG files without an error.

In general, most JPEGs taken by recent mobile phones are larger when optimized with version 4.1.1 than with 3.1.

Am I using jpegtran correctly? Is it possible to specify command-line flags for version 4.1.1 so that the output JPEG won't be larger than of 3.1?

I have tried at all command-line flags of mozjpegtran in version 4.1.1, especially -fastcrush, -restart ..., -maxmemory ..., -maxscans ... and -verbose -verbose -verbose -verbose -verbose, but they didn't make the JPEG output file lab411.jpg as small as lab31.jpg.

If not, then could you please fix it in the next version? I'd like to use jpegtran of a recent mozjpeg for lossless JPEG optimization, but I'd like to keep the output file as small as possible.

@FredWahl
Copy link

I noticed the same. I have found that it’s not a problem. After having examined the files, I found that it can add markers that should be there but are not. I have decided that if I get a file where the size isn’t exactly the same as the input file, then I accept it as better. I have created a program that automates jpegtran and I simply overwrite the input file with what I get. I think tyo should too.

What I want is a flag such that if the output is identical to the input file, then there should be no output file but you should still get 0. There’s nothing to do with such a file. All I can ”do” is to delete it.

@FredWahl
Copy link

You can use something like jpegsnoop and compare the larger file with the smaller. AllI found was some added markers. I really think they should be there. Browsers and picture viewers seem capable of dealing with such files.

@pts
Copy link
Author

pts commented Mar 17, 2023

@FredWahl: You seem to have a different problem: for you, jpegtran creates an output file larger than the input file. In this issue, the output file of jpegtran (both 3.1 and 4.1.1) is smaller than the input file (so far so good), and the problem is that the output of jpegtran 4.1.1 is larger than the output of jpegtran 3.1.

I have found that it's not a problem.

This is a file size regression: newer version of jpegtran generates larger output file, but one of the main goals of jpegtran is to make the output file small. This is problem, because as a user I'd like to use the latest jpegtran, and get the smallest possible (and feasible) valid JPEG file. Right now, to get it, I have to run many versions of jpegtran (from mozjpeg), and pick the smallest generated output file.

@pts
Copy link
Author

pts commented Mar 17, 2023

Actually, the change was introduced between version 4.0.0 and 4.0.3. I've updated my original post with this finding.

@FredWahl
Copy link

This is interesting. My beloved MacPro crashed, so I had to find a backup of the source and make it work with a newer version. Then I noticed that jepgtran could create larger files. I tried to find some documentation that expalined this but I could not. Finally, I decided that these files were probaly better and only somewhat larger.

@FredWahl
Copy link

I think the jpeg format is complicated. It could be progressive, baseline or arithmetic. There are many programs that generate them and also many that modify them in unexpected ways. Turns out that if you opened a jpeg in a Microsoft picture viewer and then saved the image somewhere, you would get a reencoded jpeg which would be different from the file you opened. Some browers are capable of opening damaged files while others fail. I have not been able to find a good open source tool that can repair jpeg files. I guess some developers are happy if they can generate somthing that their viewer can open. It doesn’t mean that it is well formed. If jpegtran can do something about it, I think it should.

A problem with the files you uploaded is that you have no idea how they may have been manipulated before you found them. I think it makes sense to use jpegsnoop or similar to see why one is larger than the other while still encoded the same way.

BTW, I have suggested that there should be a flag the prevents jpegtran from creating an output file if it hasn’t actually done anything with the input file. Such files serve no useful purpose, certainly not to my c/c++ program. It just makes things slower.

@pts
Copy link
Author

pts commented Apr 18, 2023

It looks like we (people who have commented on this issue so far) don't have a good understanding on what has changed between jpegtran 4.0.0 and 4.0.3 causing the output JPEG file to be larger (than the previous output JPEG file). I opened this issue to get the mozjpeg developers and experts involved, develop this understanding, and then (if possible), revert the change, or at least provide a command-line flag to disable it.

To facilitate this, I've run JPEG analysis tool jpegdump.py (written in Python 2.x) on both lab31.jpg and lab411.jpg. (Unfortunately I don't have a Windows machine, so I'm not able to run jpegsnoop, and it's also a GUI program, so it's not easy to copy-paste its output here.)

$ python jpegdump.py lab31.jpg 
File: lab31.jpg
  1 p=0x00000000 d=0: m=ffd8 SOI
  2 p=0x00000002 d=0: m=ffe0 APP0  l=   16 e=1.985228 a=6.615385
  3 p=0x00000014 d=0: m=ffdb DQT   l=  132 e=3.110199 a=1.131783 remark: 130/65 = 2.000000
  4 p=0x0000009a d=0: m=ffc2 SOF2  l=   17 e=3.189898 a=28.214286
  5 p=0x000000ad d=0: m=ffc4 DHT   l=   29 e=2.513061 a=1.423077
  6 p=0x000000cc d=0: m=ffda SOS   l=    8 e=0.918296 a=0.200000
                                  entropy-coded data: l=32612 e=7.963000 a=81.437490 #ff00=94
  7 p=0x0000803a d=0: m=ffc4 DHT   l=   27 e=2.255647 a=0.833333
  8 p=0x00008057 d=0: m=ffda SOS   l=    8 e=1.792481 a=6.200000
                                  entropy-coded data: l=5660 e=7.905965 a=79.222477 #ff00=2
  9 p=0x0000967d d=0: m=ffc4 DHT   l=   27 e=2.255647 a=0.583333
 10 p=0x0000969a d=0: m=ffda SOS   l=    8 e=1.792481 a=6.200000
                                  entropy-coded data: l=5305 e=7.883014 a=76.964367 #ff00=7
 11 p=0x0000ab5d d=0: m=ffc4 DHT   l=   39 e=3.868652 a=8.916667
 12 p=0x0000ab86 d=0: m=ffda SOS   l=    8 e=1.459148 a=0.600000
                                  entropy-coded data: l=31638 e=7.946863 a=82.955716 #ff00=142
 13 p=0x00012726 d=0: m=ffc4 DHT   l=   89 e=6.034785 a=35.558140
 14 p=0x00012781 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.000000
                                  entropy-coded data: l=60033 e=7.965531 a=84.297092 #ff00=214
 15 p=0x0002120c d=0: m=ffc4 DHT   l=   42 e=4.346439 a=22.666667
 16 p=0x00021238 d=0: m=ffda SOS   l=    8 e=1.792481 a=18.800000
                                  entropy-coded data: l=69396 e=7.938183 a=85.086202 #ff00=176
 17 p=0x00032156 d=0: m=ffc4 DHT   l=   38 e=3.930225 a=14.857143
 18 p=0x0003217e d=0: m=ffda SOS   l=    8 e=1.792481 a=22.200000
                                  entropy-coded data: l=161787 e=7.955153 a=86.454563 #ff00=479
 19 p=0x00059983 d=0: m=ffc4 DHT   l=   47 e=4.444755 a=14.386364
 20 p=0x000599b4 d=0: m=ffda SOS   l=    8 e=1.792481 a=3.400000
                                  entropy-coded data: l=15807 e=7.856319 a=81.338922 #ff00=44
 21 p=0x0005d77d d=0: m=ffc4 DHT   l=   53 e=4.808914 a=35.800000
 22 p=0x0005d7b4 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.400000
                                  entropy-coded data: l=1260 e=7.756143 a=85.933280 #ff00=3
 23 p=0x0005dcaa d=0: m=ffc4 DHT   l=   48 e=4.499227 a=18.022222
 24 p=0x0005dcdc d=0: m=ffda SOS   l=    8 e=1.792481 a=3.800000
                                  entropy-coded data: l=14866 e=7.878018 a=81.822402 #ff00=37
 25 p=0x000616f8 d=0: m=ffc4 DHT   l=   57 e=5.024060 a=28.129630
 26 p=0x00061733 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.800000
                                  entropy-coded data: l=1602 e=7.773306 a=85.882573 #ff00=5
 27 p=0x00061d7f d=0: m=ffd9 EOI
$ python jpegdump.py lab411.jpg 
File: lab411.jpg
  1 p=0x00000000 d=0: m=ffd8 SOI
  2 p=0x00000002 d=0: m=ffe0 APP0  l=   16 e=1.985228 a=6.615385
  3 p=0x00000014 d=0: m=ffdb DQT   l=  132 e=3.110199 a=1.131783 remark: 130/65 = 2.000000
  4 p=0x0000009a d=0: m=ffc2 SOF2  l=   17 e=3.189898 a=28.214286
  5 p=0x000000ad d=0: m=ffc4 DHT   l=   54 e=2.453368 a=1.196078
  6 p=0x000000e5 d=0: m=ffda SOS   l=   12 e=2.121928 a=6.777778
                                  entropy-coded data: l=45295 e=7.969988 a=81.819468 #ff00=66
  7 p=0x0000b1e2 d=0: m=ffc4 DHT   l=   39 e=3.868652 a=8.916667
  8 p=0x0000b20b d=0: m=ffda SOS   l=    8 e=1.459148 a=0.600000
                                  entropy-coded data: l=31638 e=7.946863 a=82.955716 #ff00=142
  9 p=0x00012dab d=0: m=ffc4 DHT   l=   89 e=6.034785 a=35.558140
 10 p=0x00012e06 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.000000
                                  entropy-coded data: l=60033 e=7.965531 a=84.297092 #ff00=214
 11 p=0x00021891 d=0: m=ffc4 DHT   l=   42 e=4.346439 a=22.666667
 12 p=0x000218bd d=0: m=ffda SOS   l=    8 e=1.792481 a=18.800000
                                  entropy-coded data: l=69396 e=7.938183 a=85.086202 #ff00=176
 13 p=0x000327db d=0: m=ffc4 DHT   l=   38 e=3.930225 a=14.857143
 14 p=0x00032803 d=0: m=ffda SOS   l=    8 e=1.792481 a=22.200000
                                  entropy-coded data: l=161787 e=7.955153 a=86.454563 #ff00=479
 15 p=0x0005a008 d=0: m=ffc4 DHT   l=   47 e=4.444755 a=14.386364
 16 p=0x0005a039 d=0: m=ffda SOS   l=    8 e=1.792481 a=3.400000
                                  entropy-coded data: l=15807 e=7.856319 a=81.338922 #ff00=44
 17 p=0x0005de02 d=0: m=ffc4 DHT   l=   53 e=4.808914 a=35.800000
 18 p=0x0005de39 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.400000
                                  entropy-coded data: l=1260 e=7.756143 a=85.933280 #ff00=3
 19 p=0x0005e32f d=0: m=ffc4 DHT   l=   48 e=4.499227 a=18.022222
 20 p=0x0005e361 d=0: m=ffda SOS   l=    8 e=1.792481 a=3.800000
                                  entropy-coded data: l=14866 e=7.878018 a=81.822402 #ff00=37
 21 p=0x00061d7d d=0: m=ffc4 DHT   l=   57 e=5.024060 a=28.129630
 22 p=0x00061db8 d=0: m=ffda SOS   l=    8 e=2.251629 a=25.800000
                                  entropy-coded data: l=1602 e=7.773306 a=85.882573 #ff00=5
 23 p=0x00062404 d=0: m=ffd9 EOI

It looks like both output files are progressive JPEGs (SOF2), they contain a the minimal JFIF segment without thumbnail (APP0), they don't contain metadata beyond the minimal JFIF segment, they contain identical quantization tables (DQT), and they do the lossless entropy coding very differently (lab31.jpg contains 11 DHT–SOS segment pairs, lab411.jpg contains 9), and the lossless entropy coding in lab31.jpg is more efficint, serving as an example for the optimized file size regression between mozjpeg 4.0.0 and 4.0.3.

mozjpeg developers, do you have any insights?

@zvezdochiot
Copy link

zvezdochiot commented Apr 18, 2023

@pts say:

Unfortunately I don't have a Windows machine, so I'm not able to run jpegsnoop, and it's also a GUI program, so it's not easy to copy-paste its output here.

https://github.com/ImageProcessing-ElectronicPublications/jpegsnoop (cross, cli).

@pts
Copy link
Author

pts commented Apr 18, 2023

@FredWahl:

I think the jpeg format is complicated. [..] I have not been able to find a good open source tool that can repair jpeg files. [...] It doesn’t mean that it is well formed. If jpegtran can do something about it, I think it should.

That sounds useful, but it's out-of-scope for this issue. You may want to open a separate issue for that.

As of now, jpegtran of mozjpeg 4.1.1 isn't a general-purpose tool to repair broken JPEG files. For example, if we assume the contrary, and we also assume that lab31.jpg is broken, and run it through jpegtran of mozjpeg 4.1.1, we get an identical output JPEG file, meaning that lab31.jpg was not broken. But then why does jpegtran of mozjpeg 4.1.1 generate a larger output JPEG file for lab.jpg if it's a JPEG optimizer and its previous version was able to generate a smaller, valid output JPEG? (That's the question I have asked and clarified many times in this issue.)

A problem with the files you uploaded is that you have no idea how they may have been manipulated before you found them.

I fail to see a problem concerning manipulation here. What I see is that I download JPEG file lab.jpg from a random source, and jpegtran of mozjpeg 4.1.1 creates an optimized output JPEG file which is larger than the optimized output JPEG file created by jpegtran of mozjpeg 3.1 for the same lab.jpg file. It doesn't matter how the original JPEG file lab.jpg was created (and/or manipulated), a JPEG optimizer (such as jpegtran of mozjpeg) shouldn't have such a file size regression, at least not without the developers understanding and documenting it. Generating larger output files without a good reason defeats the purpose of the JPEG optimizer.

there should be a flag the prevents jpegtran from creating an output file if it hasn’t actually done anything with the input file

That also sounds useful for some use cases, but it's also out-of-scope for this issue, and it's also independent from it (fixing one doesn't help the other). You may want to open a separate issue for that, thus they can be fixed independently.

@pts
Copy link
Author

pts commented Apr 18, 2023

@zvezdochiot say:

Unfortunately I don't have a Windows machine, so I'm not able to run jpegsnoop, and it's also a GUI program, so it's not easy to copy-paste its output here.

https://github.com/ImageProcessing-ElectronicPublications/jpegsnoop (cross, cli).

Thank you! I've run jpegsnoop and added links to its output to the issue opening message.

@pkyeck
Copy link

pkyeck commented Jun 15, 2023

I have a similar problem with cjpeg: updated from 3.1 to 4.1.3 and when I run the same command, I get way bigger output.

# mozjpeg version 3.1 (build 20170209)
cjpeg -quality 90 ./fixture.jpg > test1.jpg

# generated file
83798 Jun 15 13:38 test1.jpg
# mozjpeg version 4.1.3 (build 20230613)
cjpeg -quality 90 ./fixture.jpg > test1.jpg

# generated file
100387 Jun 15 13:40 test1.jpg

And I also had the case that the input jpg was smaller than the output jpg with quality set to 90 ... 🤔

what can we do about this?

did some defaults change that we now have to set manually?

@FredWahl
Copy link

So the problem affects both cjpeg and jpegtran? That seems to indicate that the problem lies in some routine that is common. It is a good thing that people pay attention to detail. In my case it was by accident. I’m surprised this was not found out by the developers. It seems like a good idea to run a newer tool on the output picture of an older tool as well as using identical input files. It quickly becomes complicated when you have many tools and many commands. The tools I have used as a developer would not suffice, I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants