Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create JPEG thumb image with general fixed header

I want to create preview thumb for my photos like Facebook's preview photo. My plan:

  • Sender: generate a scaled thumb (with 30px max dimension) from original photo, strip out all fixed header to send.
  • Receiver: From the "minified" byte array, append with the fixed header (hardcode in the client code). Then convert it to Bitmap to display.

Finally I come up with the solution base on Q42.ImagePreview.

I split these parts as fixed header:

  • Start Of Image (0xFFD8)
  • App0 (start with 0xFFE0)
  • Define Quantization Table(s)
  • Define Huffman Table(s)

The dynamic parts are:

  • Start Of Frame (start with 0xFFC0): because it contains the width/height bytes.
  • Start Of Scan (start with 0xFFDA).
  • Compressed image data.
  • End Of Image (0xFFD9)

But it only works on 1 of my devices & not works on others.

So how to generate a fixed, general & standard JPEG header that can use on both Android & iOS devices?

Thank you.


More detail:

Generate minified data flow:

  • Create a scaled bitmap from original image (max dimension 30px, keep aspect ratio) using BitmapFactory & Matrix

  • Compress scaled bitmap with quality 64 using Bitmap#compress() and store in byte[] thumbData.

  • Sub-array the thumbData above from 0xFFDA to the end. (SOS, image data & EOI) and store in byte[] body.

  • Prepend with the 4 bytes that repsent width & height to body, convert to Base64 string & send.

In the device that working fine, the size of thumbData is longer than others devices that not work. And the different is in Huffman Table(s), SOS & image data parts, see this: Diff check between 2 image photos

like image 964
nhoxbypass Avatar asked May 21 '19 10:05

nhoxbypass


2 Answers

I'm afraid you cannot do that using built-in method of each platform. The problem is in the compressing phase.

There are a number of variables in JPEG compression, including the type and breakdown of scans, samples, DHT selection, and DQT selection. If any of those are different in the encoder you use, you are going to get different output. It's the nature of the beast.

For example: The Define Huffman Table (DHT) define how the "image data" (after SoS segment) was compressed. And you use fixed Huffman tables only for decoding, that’s what caused the problem.


So you may have some options to choose:

  • Send the full quality image (without compressing) after scaled down to max dimension 30px as preview thumb photo.
  • Write your own compression algorithm or use a cross-platform library.
  • Upload entire original image to your server to process & send back the "minified data" to Android/iOS.

Telegram has preview photo too, and their approach is similar to you. But they transfer entire original image (in byte array) to server, create a thumb photo, strip out "fixed header" and send back to the receivers the "minified data".

When receive on mobile, they decode the "minified data" to bitmap, by appending it with "fixed header" (Bitmaps.java#L111) & update image size in SoF segment. See ImageLoader.java#L750.

like image 98
Nha T. Tran Avatar answered Sep 21 '22 22:09

Nha T. Tran


Point 1:

"If I don't split the fixed header & send the max 30x30 image with quality 64 (using bitmap.compress() too), it still works fine both platform (and the size is only 1-2 Kb).

But what I want is even smaller, that why I need to split DQT & DHT as fixed header"

  • Make 30x30 image (Bitmap)
  • Compress Bitmap into JPEG
  • Remove DQT and DHT (optional step for even smaller bytes, but removing may cause issue)
  • Compress remaining JPEG data using Deflate algorithm (basically ZIP'ing the data before sending)

Point 2:

These two images are 30x30 and use the exact same Huffman & Quantisation Tables.

image1
https://www.dropbox.com/s/qzptp9mmrhxxsq3/30x30_thumb_01.jpg?dl=1

image2
https://www.dropbox.com/s/yrvsybb564mw2vv/30x30_thumb_02.jpg?dl=1

Check if they display okay for you on iPhone and Android. If yes, then try these DQT and DHT tables provided further below on your own JPEGs.

Tables: (total size= 570 bytes)...

Define Quantization table (total size= 138 bytes) : There are two, each one begins with bytes FF DB:

FF DB 00 43 00 08 06 06 07 06 05 08 07 07 07 09 09 08 0A 0C 14 0D 0C 0B 0B 0C 19 12 13 0F 14 1D 1A 1F 1E 1D 1A 1C 1C 20 24 2E 27 20 22 2C 23 1C 1C 28 37 29 2C 30 31 34 34 34 1F 27 39 3D 38 32 3C 2E 33 34 32

FF DB 00 43 01 09 09 09 0C 0B 0C 18 0D 0D 18 32 21 1C 21 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32

Define Huffman table (total size= 432 bytes) : There are four, each one begins with bytes FF C4:

FF C4 00 1F 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B

FF C4 00 B5 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1 15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA

FF C4 00 1F 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B

FF C4 00 B5 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09 23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA

Point 3:

"So how to generate a fixed, general & standard JPEG header that can use on both Android & iOS devices?"

Try this:

(1) These beginning bytes are same for those above 30x30 images :

FF D8 FF E0 00 10 4A 46 49 46 00 01 01 01 00 60 00 60 00 00 FF DB 00 43 00 08 06 06 07 06 05 08 07 07 07 09 09 08 0A 0C 14 0D 0C 0B 0B 0C 19 12 13 0F 14 1D 1A 1F 1E 1D 1A 1C 1C 20 24 2E 27 20 22 2C 23 1C 1C 28 37 29 2C 30 31 34 34 34 1F 27 39 3D 38 32 3C 2E 33 34 32 FF DB 00 43 01 09 09 09 0C 0B 0C 18 0D 0D 18 32 21 1C 21 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 32 FF C0 00 11 08 00 1E 00 1E 03 01 22 00 02 11 01 03 11 01 FF C4 00 1F 00 00 01 05 01 01 01 01 01 01 00 00 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 10 00 02 01 03 03 02 04 03 05 05 04 04 00 00 01 7D 01 02 03 00 04 11 05 12 21 31 41 06 13 51 61 07 22 71 14 32 81 91 A1 08 23 42 B1 C1 15 52 D1 F0 24 33 62 72 82 09 0A 16 17 18 19 1A 25 26 27 28 29 2A 34 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E1 E2 E3 E4 E5 E6 E7 E8 E9 EA F1 F2 F3 F4 F5 F6 F7 F8 F9 FA FF C4 00 1F 01 00 03 01 01 01 01 01 01 01 01 01 00 00 00 00 00 00 01 02 03 04 05 06 07 08 09 0A 0B FF C4 00 B5 11 00 02 01 02 04 04 03 04 07 05 04 04 00 01 02 77 00 01 02 03 11 04 05 21 31 06 12 41 51 07 61 71 13 22 32 81 08 14 42 91 A1 B1 C1 09 23 33 52 F0 15 62 72 D1 0A 16 24 34 E1 25 F1 17 18 19 1A 26 27 28 29 2A 35 36 37 38 39 3A 43 44 45 46 47 48 49 4A 53 54 55 56 57 58 59 5A 63 64 65 66 67 68 69 6A 73 74 75 76 77 78 79 7A 82 83 84 85 86 87 88 89 8A 92 93 94 95 96 97 98 99 9A A2 A3 A4 A5 A6 A7 A8 A9 AA B2 B3 B4 B5 B6 B7 B8 B9 BA C2 C3 C4 C5 C6 C7 C8 C9 CA D2 D3 D4 D5 D6 D7 D8 D9 DA E2 E3 E4 E5 E6 E7 E8 E9 EA F2 F3 F4 F5 F6 F7 F8 F9 FA

(2) After above header's last four bytes F7 F8 F9 FA comes Start of Scan marker (FF DA) with 14 bytes:

FF DA 00 0C 03 01 00 02 11 03 11 00 3F 00

(3) Now add your JPEG scan data up to ending FF D9 bytes.

Basically in your .compress() output's JPEG, Delete all bytes from FF D8 up to FF DA + 12 more following bytes. This way you have removed header and DHT/DQT tables. Send this smaller data and on the receiving side your app just puts header bytes from steps (1) and step (2) into some array then also adding your received bytes after the header.

Try to now load the re-fixed JPEG.
(your array should be a complete JPEG data, with bytes starting from FF D8 ending with FF D9).

like image 34
VC.One Avatar answered Sep 23 '22 22:09

VC.One