最小的有效JPEG文件大小是多少字节?

39

在将JPEG图像通过网络进行详细检查之前,我希望先对其进行有效性筛选。检查其有效的头部和尾部很容易,但是一个有效的JPEG图像最小可能有多少字节?


3
libjpeg可以进行快速测试,考虑使用它而不是猜测。 - Tronic
4
我不想在我的应用程序中添加任何额外的库。此外,如果有人告诉我正确的答案,那就不是猜测了 :) - twk
1
你应该把你的问题改成“测试一下某些JPEG文件是否有效”,除非你打算在文件大小测试通过后进行其他一系列的测试。否则,很容易生成任何大于有效JPEG最小大小的无效JPEG。 - jball
@jball,好主意-我已经澄清了问题。 - twk
7个回答

32

使用算术编码在125个字节中表示1x1灰色像素,即使大多数解码器无法解码,它仍然符合JPEG标准:

ff d8 : SOI
ff e0 ; APP0
 00 10
 4a 46 49 46 00 01 01 01 00 48 00 48 00 00
ff db ; DQT
 00 43
 00
 03 02 02 02 02 02 03 02
 02 02 03 03 03 03 04 06
 04 04 04 04 04 08 06 06
 05 06 09 08 0a 0a 09 08
 09 09 0a 0c 0f 0c 0a 0b
 0e 0b 09 09 0d 11 0d 0e
 0f 10 10 11 10 0a 0c 12
 13 12 10 13 0f 10 10 10
ff c9 ; SOF
 00 0b
 08 00 01 00 01 01 01 11 00
ff cc ; DAC
 00 06 00 10 10 05
ff da ; SOS
 00 08
 01 01 00 00 3f 00 d2 cf 20
ff d9 ; EOI

我认为提到的134字节示例不是标准的,因为它缺少一个EOI。所有解码器都可以处理这个问题,但标准规定应该以一个EOI结尾。

可以使用以下命令生成该文件:

#!/usr/bin/env bash
printf '\xff\xd8' # SOI
printf '\xff\xe0' # APP0
printf  '\x00\x10'
printf  '\x4a\x46\x49\x46\x00\x01\x01\x01\x00\x48\x00\x48\x00\x00'
printf '\xff\xdb' # DQT
printf  '\x00\x43'
printf  '\x00'
printf  '\x03\x02\x02\x02\x02\x02\x03\x02'
printf  '\x02\x02\x03\x03\x03\x03\x04\x06'
printf  '\x04\x04\x04\x04\x04\x08\x06\x06'
printf  '\x05\x06\x09\x08\x0a\x0a\x09\x08'
printf  '\x09\x09\x0a\x0c\x0f\x0c\x0a\x0b'
printf  '\x0e\x0b\x09\x09\x0d\x11\x0d\x0e'
printf  '\x0f\x10\x10\x11\x10\x0a\x0c\x12'
printf  '\x13\x12\x10\x13\x0f\x10\x10\x10'
printf '\xff\xc9' # SOF
printf  '\x00\x0b'
printf  '\x08\x00\x01\x00\x01\x01\x01\x11\x00'
printf '\xff\xcc' # DAC
printf  '\x00\x06\x00\x10\x10\x05'
printf '\xff\xda' # SOS
printf  '\x00\x08'
printf  '\x01\x01\x00\x00\x3f\x00\xd2\xcf\x20'
printf '\xff\xd9' # EOI

这张图片在Ubuntu 20.10上可以使用GNOME Image Viewer 3.38.0和GIMP 2.10.18打开。

生成此图像的另一种方法:
echo ffd8ffe000104a46494600010101004800480000ffdb004300030202020202030202020303030304060404040404080606050609080a0a090809090a0c0f0c0a0b0e0b09090d110d0e0f101011100a0c12131210130f101010ffc9000b080001000101011100ffcc000600101005ffda0008010100003f00d2cf20ffd9 | xxd -r -p > small.jpg

这里有一个上传到Imgur的链接。请注意,Imgur会处理文件使其变大,但是如果您下载它进行检查,并且如下所示,width=100的图像在Chromium 87上显示为白色:


4
哪些字节是安全的增加以产生一系列小但不同的JPEG文件? - Quolonel Questions
@Quolonel 问题 - DQT 分段中的 8x8 '方块' 字节实际上是缩放因子,其中任何一个因子都可以是值 1-255。 我认为在此示例的 DAC 分段中使用的唯一值是位于 8x8 块左上角的第一个值。 - matja
它在Windows上不起作用,为什么? - Eugene W.

14

我想到了一种方法,可以仅使用DC系数生成渐进式JPEG图像,一个单一的灰色像素可以被编码成119个字节。在我试过的几个程序中(如Photoshop、GNOME Image Viewer 3.38.0和GIMP 2.10.18等),这种方法表现得很好。

ff d8 : SOI
ff db ; DQT
 00 43
 00
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
 01 01 01 01 01 01 01 01
ff c2 ; SOF
 00 0b
 08 00 01 00 01 01 01 11 00
ff c4 ; DHT
 00 14
 00
 01 00 00 00 00 00 00 00
 00 00 00 00 00 00 00 00
 03
ff da ; SOS
 00 08
 01 01 00 00 00 01 3F
ff d9 ; EOI

主要的空间节省在于只有一个霍夫曼表。虽然这比另一篇答案中给出的125字节算术编码稍微小一些,但去掉JFIF头的算术编码会更小(107字节),因此应该仍然被认为是已知最小的。

可以使用以下命令生成上述文件:

#!/usr/bin/env bash
printf '\xff\xd8' # SOI
printf '\xff\xdb' # DQT
printf  '\x00\x43'
printf  '\x00'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf  '\x01\x01\x01\x01\x01\x01\x01\x01'
printf '\xff\xc2' # SOF
printf  '\x00\x0b'
printf  '\x08\x00\x01\x00\x01\x01\x01\x11\x00'
printf '\xff\xc4' # DHT
printf  '\x00\x14'
printf  '\x00'
printf  '\x01\x00\x00\x00\x00\x00\x00\x00'
printf  '\x00\x00\x00\x00\x00\x00\x00\x00'
printf  '\x03'
printf '\xff\xda' # SOS
printf  '\x00\x08'
printf  '\x01\x01\x00\x00\x00\x01\x3F'
printf '\xff\xd9' # EOI

对于好奇的人,当尝试使用iOS的[UIImage imageWithData:]读取此内容时,它会输出:ImageIO:JPEG Corrupt JPEG data:2 extraneous bytes before marker 0xda - Ricardo Sanchez-Saez
1
或者作为数据链接data:image/jpeg,%ff%d8%ff%db%00%43%00%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%01%ff%c2F%00%0b%08%00%01%00%01%01%01%11%00%ff%c4%00%14%00%01%00%00%00%00%00%00%00%00%00%00%00%00%00%00%00%03%ff%da%00%08%01%01%00%00%00%01%3F%ff%d9 - LeBleu
...%ff %c2F %00... 应该是238个十六进制字符,即ffd8ffdb00430001010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101010101ffc2000b080001000101011100ffc40014000100000000000000000000000000000003ffda00080101000000013fffd9 - undefined

9
请尝试以下代码 (134字节):
FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 48 00 48 00 00
FF DB 00 43 00 FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF FF
FF FF FF FF FF FF FF FF FF FF C2 00 0B 08 00 01 00 01 01 01
11 00 FF C4 00 14 10 01 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 FF DA 00 08 01 01 00 01 3F 10

Source: Worlds Smallest, Valid JPEG? by Jesse_hz


7

找到了一张仅有26字节大小的“史上最小”的GIF图像

47 49 46 38 39 61 01 00 01 00 
00 ff 00 2c 00 00 00 00 01 00 
01 00 00 02 00 3b

Python字面值:

b'GIF89a\x01\x00\x01\x00\x00\xff\x00,\x00\x00\x00\x00\x01\x00\x01\x00\x00\x02\x00;'

1
GIF和JPG之间是否存在正式的关系? - Ciro Santilli OurBigBook.com
1
@CiroSantilliTRUMPBANISBAD Python。 - Nakilon
Python PIL 给我报错 OSError: image file is truncated (1 bytes not processed) - Eugene W.
然而,编写from PIL import ImageFile ImageFile.LOAD_TRUNCATED_IMAGES = True对我有帮助。 - Eugene W.

3
虽然我知道这不是最小的有效JPEG,也与你实际问题无关,但我觉得我应该分享一下,因为当我发现你的问题时,一直在寻找一个非常小的JPEG来进行测试。我在这里分享它,因为它是有效的、很小,并且让我ROFL(大笑)。
这是我在Photoshop中制作的一个384字节的JPEG图像。它是我手绘的“ROFL”字母,并在最大压缩设置下保存,仍然有点可读。
十六进制序列:
my @image_hex = qw{
 FF D8 FF E0 00 10 4A 46 49 46 00 01 02 00 00 64
 00 64 00 00 FF EC 00 11 44 75 63 6B 79 00 01 00
 04 00 00 00 00 00 00 FF EE 00 0E 41 64 6F 62 65
 00 64 C0 00 00 00 01 FF DB 00 84 00 1B 1A 1A 29
 1D 29 41 26 26 41 42 2F 2F 2F 42 47 3F 3E 3E 3F
 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47
 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47
 47 47 47 47 47 47 47 47 47 47 47 47 01 1D 29 29
 34 26 34 3F 28 28 3F 47 3F 35 3F 47 47 47 47 47
 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47
 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47 47
 47 47 47 47 47 47 47 47 47 47 47 47 47 FF C0 00
 11 08 00 08 00 19 03 01 22 00 02 11 01 03 11 01
 FF C4 00 61 00 01 01 01 01 00 00 00 00 00 00 00
 00 00 00 00 00 00 04 02 05 01 01 01 01 00 00 00
 00 00 00 00 00 00 00 00 00 00 00 02 04 10 00 02
 02 02 02 03 01 00 00 00 00 00 00 00 00 00 01 02
 11 03 00 41 21 12 F0 13 04 31 11 00 01 04 03 00
 00 00 00 00 00 00 00 00 00 00 00 00 21 31 61 71
 B1 12 22 FF DA 00 0C 03 01 00 02 11 03 11 00 3F
 00 A1 7E 6B AD 4E B6 4B 30 EA E0 19 82 39 91 3A
 6E 63 5F 99 8A 68 B6 E3 EA 70 08 A8 00 55 98 EE
 48 22 37 1C 63 19 AF A5 68 B8 05 24 9A 7E 99 F5
 B3 22 20 55 EA 27 CD 8C EB 4E 31 91 9D 41 FF D9
}; #this is a very tiny jpeg. it is a image representaion of the letters "ROFL" hand drawn by me in photoshop and then saved at the lowest possible quality settings where the letters could still be made out :)

my $image_data = pack('H2' x scalar(@image_hex), @image_hex);
my $url_escaped_image = uri_escape( $image_data );

URL转义的二进制图像数据(可以直接粘贴到URL中)

%FF%D8%FF%E0%00%10JFIF%00%01%02%00%00d%00d%00%00%FF%EC%00%11Ducky%00%01%00%04%00%00%00%00%00%00%FF%EE%00%0EAdobe%00d%C0%00%00%00%01%FF%DB%00%84%00%1B%1A%1A)%1D)A%26%26AB%2F%2F%2FBG%3F%3E%3E%3FGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG%01%1D))4%264%3F((%3FG%3F5%3FGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG%FF%C0%00%11%08%00%08%00%19%03%01%22%00%02%11%01%03%11%01%FF%C4%00a%00%01%01%01%01%00%00%00%00%00%00%00%00%00%00%00%00%00%04%02%05%01%01%01%01%00%00%00%00%00%00%00%00%00%00%00%00%00%00%02%04%10%00%02%02%02%02%03%01%00%00%00%00%00%00%00%00%00%01%02%11%03%00A!%12%F0%13%041%11%00%01%04%03%00%00%00%00%00%00%00%00%00%00%00%00%00!1aq%B1%12%22%FF%DA%00%0C%03%01%00%02%11%03%11%00%3F%00%A1~k%ADN%B6K0%EA%E0%19%829%91%3Anc_%99%8Ah%B6%E3%EAp%08%A8%00U%98%EEH%227%1Cc%19%AF%A5h%B8%05%24%9A~%99%F5%B3%22%20U%EA'%CD%8C%EBN1%91%9DA%FF%D9

1
这是我编写的用于执行此操作的C++例程:
bool is_jpeg(const unsigned char* img_data, size_t size)
{           
    return img_data &&
           (size >= 10) &&
           (img_data[0] == 0xFF) &&
           (img_data[1] == 0xD8) &&
           ((memcmp(img_data + 6, "JFIF", 4) == 0) ||
            (memcmp(img_data + 6, "Exif", 4) == 0));
}

img_data指向一个包含JPEG数据的缓冲区。

我确定你需要更多的字节才能得到一个可以解码成有用图像的JPEG,但如果前10个字节通过了这个测试,那么这个缓冲区很可能包含一个JPEG。

编辑:当你决定一个值后,你当然可以将上面的10替换为更高的值。例如另一个答案中建议的134。


0

JPEGs并不一定需要包含JFIF或Exif标记。但是它们必须以FF D8开头,并且必须有一个跟随其后的标记,这样您就可以检查FF D8 FF是否存在。


2
这是一个很好的评论,但并没有回答问题。考虑将其放在另一个答案下面。 - Brent Faust

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接