JPEGWriter.cpp 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656
  1. /*
  2. * Copyright (c) 2023, Lucas Chollet <lucas.chollet@serenityos.org>
  3. *
  4. * SPDX-License-Identifier: BSD-2-Clause
  5. */
  6. #include "JPEGWriter.h"
  7. #include "JPEGShared.h"
  8. #include "JPEGWriterTables.h"
  9. #include <AK/BitStream.h>
  10. #include <AK/Endian.h>
  11. #include <AK/Function.h>
  12. #include <LibGfx/Bitmap.h>
  13. #include <LibGfx/CMYKBitmap.h>
  14. namespace Gfx {
  15. namespace {
  16. enum Mode {
  17. RGB,
  18. CMYK,
  19. };
  20. // This is basically a BigEndianOutputBitStream, the only difference
  21. // is that it appends 0x00 after each 0xFF when it writes bits.
  22. class JPEGBigEndianOutputBitStream : public Stream {
  23. public:
  24. explicit JPEGBigEndianOutputBitStream(Stream& stream)
  25. : m_stream(stream)
  26. {
  27. }
  28. virtual ErrorOr<Bytes> read_some(Bytes) override
  29. {
  30. return Error::from_errno(EBADF);
  31. }
  32. virtual ErrorOr<size_t> write_some(ReadonlyBytes bytes) override
  33. {
  34. VERIFY(m_bit_offset == 0);
  35. return m_stream.write_some(bytes);
  36. }
  37. template<Unsigned T>
  38. ErrorOr<void> write_bits(T value, size_t bit_count)
  39. {
  40. VERIFY(m_bit_offset <= 7);
  41. while (bit_count > 0) {
  42. u8 const next_bit = (value >> (bit_count - 1)) & 1;
  43. bit_count--;
  44. m_current_byte <<= 1;
  45. m_current_byte |= next_bit;
  46. m_bit_offset++;
  47. if (m_bit_offset > 7) {
  48. TRY(m_stream.write_value(m_current_byte));
  49. if (m_current_byte == 0xFF)
  50. TRY(m_stream.write_value<u8>(0));
  51. m_bit_offset = 0;
  52. m_current_byte = 0;
  53. }
  54. }
  55. return {};
  56. }
  57. virtual bool is_eof() const override
  58. {
  59. return true;
  60. }
  61. virtual bool is_open() const override
  62. {
  63. return m_stream.is_open();
  64. }
  65. virtual void close() override
  66. {
  67. }
  68. ErrorOr<void> align_to_byte_boundary(u8 filler = 0x0)
  69. {
  70. if (m_bit_offset == 0)
  71. return {};
  72. TRY(write_bits(filler, 8 - m_bit_offset));
  73. VERIFY(m_bit_offset == 0);
  74. return {};
  75. }
  76. private:
  77. Stream& m_stream;
  78. u8 m_current_byte { 0 };
  79. size_t m_bit_offset { 0 };
  80. };
  81. class JPEGEncodingContext {
  82. public:
  83. JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream)
  84. : m_bit_stream(move(output_stream))
  85. {
  86. }
  87. ErrorOr<void> initialize_mcu(Bitmap const& bitmap)
  88. {
  89. u64 const horizontal_macroblocks = ceil_div(bitmap.width(), 8);
  90. u64 const vertical_macroblocks = ceil_div(bitmap.height(), 8);
  91. TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks));
  92. for (u16 y {}; y < bitmap.height(); ++y) {
  93. u16 const vertical_macroblock_index = y / 8;
  94. u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
  95. for (u16 x {}; x < bitmap.width(); ++x) {
  96. u16 const horizontal_macroblock_index = x / 8;
  97. u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8;
  98. auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index];
  99. auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset;
  100. auto const original_pixel = bitmap.get_pixel(x, y);
  101. // Conversion from YCbCr to RGB isn't specified in the first JPEG specification but in the JFIF extension:
  102. // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items
  103. // 7 - Conversion to and from RGB
  104. auto const y_ = clamp(0.299 * original_pixel.red() + 0.587 * original_pixel.green() + 0.114 * original_pixel.blue(), 0, 255);
  105. auto const cb = clamp(-0.1687 * original_pixel.red() - 0.3313 * original_pixel.green() + 0.5 * original_pixel.blue() + 128, 0, 255);
  106. auto const cr = clamp(0.5 * original_pixel.red() - 0.4187 * original_pixel.green() - 0.0813 * original_pixel.blue() + 128, 0, 255);
  107. // A.3.1 - Level shift
  108. macroblock.r[pixel_offset] = y_ - 128;
  109. macroblock.g[pixel_offset] = cb - 128;
  110. macroblock.b[pixel_offset] = cr - 128;
  111. }
  112. }
  113. return {};
  114. }
  115. ErrorOr<void> initialize_mcu(CMYKBitmap const& bitmap)
  116. {
  117. u64 const horizontal_macroblocks = ceil_div(bitmap.size().width(), 8);
  118. u64 const vertical_macroblocks = ceil_div(bitmap.size().height(), 8);
  119. TRY(m_macroblocks.try_resize(horizontal_macroblocks * vertical_macroblocks));
  120. for (u16 y {}; y < bitmap.size().height(); ++y) {
  121. u16 const vertical_macroblock_index = y / 8;
  122. u16 const vertical_pixel_offset = y - vertical_macroblock_index * 8;
  123. for (u16 x {}; x < bitmap.size().width(); ++x) {
  124. u16 const horizontal_macroblock_index = x / 8;
  125. u16 const horizontal_pixel_offset = x - horizontal_macroblock_index * 8;
  126. auto& macroblock = m_macroblocks[vertical_macroblock_index * horizontal_macroblocks + horizontal_macroblock_index];
  127. auto const pixel_offset = vertical_pixel_offset * 8 + horizontal_pixel_offset;
  128. auto const original_pixel = bitmap.scanline(y)[x];
  129. // To get YCCK, the CMY part is converted to RGB (ignoring the K component), and then the RGB is converted to YCbCr.
  130. // r is `255 - c` (and similar for g/m b/y), but with the Adobe YCCK color transform marker, the CMY
  131. // channels are stored inverted, which cancels out: 255 - (255 - x) == x.
  132. // K is stored as-is (meaning it's inverted once for the color transform).
  133. u8 r = original_pixel.c;
  134. u8 g = original_pixel.m;
  135. u8 b = original_pixel.y;
  136. u8 k = 255 - original_pixel.k;
  137. // See: https://www.itu.int/rec/dologin_pub.asp?lang=f&id=T-REC-T.871-201105-I!!PDF-E&type=items
  138. // 7 - Conversion to and from RGB
  139. auto const y_ = clamp(0.299 * r + 0.587 * g + 0.114 * b, 0, 255);
  140. auto const cb = clamp(-0.1687 * r - 0.3313 * g + 0.5 * b + 128, 0, 255);
  141. auto const cr = clamp(0.5 * r - 0.4187 * g - 0.0813 * b + 128, 0, 255);
  142. // A.3.1 - Level shift
  143. macroblock.r[pixel_offset] = y_ - 128;
  144. macroblock.g[pixel_offset] = cb - 128;
  145. macroblock.b[pixel_offset] = cr - 128;
  146. macroblock.k[pixel_offset] = k - 128;
  147. }
  148. }
  149. return {};
  150. }
  151. static Array<double, 64> create_cosine_lookup_table()
  152. {
  153. static constexpr double pi_over_16 = AK::Pi<double> / 16;
  154. Array<double, 64> table;
  155. for (u8 u = 0; u < 8; ++u) {
  156. for (u8 x = 0; x < 8; ++x)
  157. table[u * 8 + x] = cos((2 * x + 1) * u * pi_over_16);
  158. }
  159. return table;
  160. }
  161. void fdct_and_quantization(Mode mode)
  162. {
  163. static auto cosine_table = create_cosine_lookup_table();
  164. for (auto& macroblock : m_macroblocks) {
  165. constexpr double inverse_sqrt_2 = M_SQRT1_2;
  166. auto const convert_one_component = [&](i16 component[], QuantizationTable const& table) {
  167. Array<i16, 64> result {};
  168. auto const sum_xy = [&](u8 u, u8 v) {
  169. double sum {};
  170. for (u8 y {}; y < 8; ++y) {
  171. for (u8 x {}; x < 8; ++x)
  172. sum += component[y * 8 + x] * cosine_table[u * 8 + x] * cosine_table[v * 8 + y];
  173. }
  174. return sum;
  175. };
  176. for (u8 v {}; v < 8; ++v) {
  177. double const cv = v == 0 ? inverse_sqrt_2 : 1;
  178. for (u8 u {}; u < 8; ++u) {
  179. auto const table_index = v * 8 + u;
  180. double const cu = u == 0 ? inverse_sqrt_2 : 1;
  181. // A.3.3 - FDCT and IDCT
  182. double const fdct = cu * cv * sum_xy(u, v) / 4;
  183. // A.3.4 - DCT coefficient quantization
  184. i16 const quantized = round(fdct / table.table[table_index]);
  185. result[table_index] = quantized;
  186. }
  187. }
  188. for (u8 i {}; i < result.size(); ++i)
  189. component[i] = result[i];
  190. };
  191. convert_one_component(macroblock.y, m_luminance_quantization_table);
  192. convert_one_component(macroblock.cb, m_chrominance_quantization_table);
  193. convert_one_component(macroblock.cr, m_chrominance_quantization_table);
  194. if (mode == Mode::CMYK)
  195. convert_one_component(macroblock.k, m_luminance_quantization_table);
  196. }
  197. }
  198. ErrorOr<void> write_huffman_stream(Mode mode)
  199. {
  200. for (auto& macroblock : m_macroblocks) {
  201. TRY(encode_dc(dc_luminance_huffman_table, macroblock.y, 0));
  202. TRY(encode_ac(ac_luminance_huffman_table, macroblock.y));
  203. TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cb, 1));
  204. TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cb));
  205. TRY(encode_dc(dc_chrominance_huffman_table, macroblock.cr, 2));
  206. TRY(encode_ac(ac_chrominance_huffman_table, macroblock.cr));
  207. if (mode == Mode::CMYK) {
  208. TRY(encode_dc(dc_luminance_huffman_table, macroblock.k, 3));
  209. TRY(encode_ac(ac_luminance_huffman_table, macroblock.k));
  210. }
  211. }
  212. TRY(m_bit_stream.align_to_byte_boundary(0xFF));
  213. return {};
  214. }
  215. void set_luminance_quantization_table(QuantizationTable const& table, int quality)
  216. {
  217. set_quantization_table(m_luminance_quantization_table, table, quality);
  218. }
  219. void set_chrominance_quantization_table(QuantizationTable const& table, int quality)
  220. {
  221. set_quantization_table(m_chrominance_quantization_table, table, quality);
  222. }
  223. QuantizationTable const& luminance_quantization_table() const
  224. {
  225. return m_luminance_quantization_table;
  226. }
  227. QuantizationTable const& chrominance_quantization_table() const
  228. {
  229. return m_chrominance_quantization_table;
  230. }
  231. OutputHuffmanTable dc_luminance_huffman_table;
  232. OutputHuffmanTable dc_chrominance_huffman_table;
  233. OutputHuffmanTable ac_luminance_huffman_table;
  234. OutputHuffmanTable ac_chrominance_huffman_table;
  235. private:
  236. static void set_quantization_table(QuantizationTable& destination, QuantizationTable const& source, int quality)
  237. {
  238. // In order to be compatible with libjpeg-turbo, we use the same coefficients as them.
  239. quality = clamp(quality, 1, 100);
  240. if (quality < 50)
  241. quality = 5000 / quality;
  242. else
  243. quality = 200 - quality * 2;
  244. destination = source;
  245. for (u8 i {}; i < 64; ++i) {
  246. auto const shifted_value = (destination.table[i] * quality + 50) / 100;
  247. destination.table[i] = clamp(shifted_value, 1, 255);
  248. }
  249. }
  250. ErrorOr<void> write_symbol(OutputHuffmanTable::Symbol symbol)
  251. {
  252. return m_bit_stream.write_bits(symbol.word, symbol.code_length);
  253. }
  254. ErrorOr<void> encode_dc(OutputHuffmanTable const& dc_table, i16 const component[], u8 component_id)
  255. {
  256. // F.1.2.1.3 - Huffman encoding procedures for DC coefficients
  257. auto diff = component[0] - m_last_dc_values[component_id];
  258. m_last_dc_values[component_id] = component[0];
  259. auto const size = csize(diff);
  260. TRY(write_symbol(dc_table.from_input_byte(size)));
  261. if (diff < 0)
  262. diff -= 1;
  263. TRY(m_bit_stream.write_bits<u16>(diff, size));
  264. return {};
  265. }
  266. ErrorOr<void> encode_ac(OutputHuffmanTable const& ac_table, i16 const component[])
  267. {
  268. {
  269. // F.2 - Procedure for sequential encoding of AC coefficients with Huffman coding
  270. u32 k {};
  271. u32 r {};
  272. while (k < 63) {
  273. k++;
  274. auto coefficient = component[zigzag_map[k]];
  275. if (coefficient == 0) {
  276. if (k == 63) {
  277. TRY(write_symbol(ac_table.from_input_byte(0x00)));
  278. break;
  279. }
  280. r += 1;
  281. continue;
  282. }
  283. while (r > 15) {
  284. TRY(write_symbol(ac_table.from_input_byte(0xF0)));
  285. r -= 16;
  286. }
  287. {
  288. // F.3 - Sequential encoding of a non-zero AC coefficient
  289. auto const ssss = csize(coefficient);
  290. auto const rs = (r << 4) + ssss;
  291. TRY(write_symbol(ac_table.from_input_byte(rs)));
  292. if (coefficient < 0)
  293. coefficient -= 1;
  294. TRY(m_bit_stream.write_bits<u16>(coefficient, ssss));
  295. }
  296. r = 0;
  297. }
  298. }
  299. return {};
  300. }
  301. static u8 csize(i16 coefficient)
  302. {
  303. VERIFY(coefficient >= -2047 && coefficient <= 2047);
  304. if (coefficient == 0)
  305. return 0;
  306. return floor(log2(abs(coefficient))) + 1;
  307. }
  308. QuantizationTable m_luminance_quantization_table {};
  309. QuantizationTable m_chrominance_quantization_table {};
  310. Vector<Macroblock> m_macroblocks {};
  311. Array<i16, 4> m_last_dc_values {};
  312. JPEGBigEndianOutputBitStream m_bit_stream;
  313. };
  314. ErrorOr<void> add_start_of_image(Stream& stream)
  315. {
  316. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOI));
  317. return {};
  318. }
  319. ErrorOr<void> add_end_of_image(Stream& stream)
  320. {
  321. TRY(stream.write_value<BigEndian<Marker>>(JPEG_EOI));
  322. return {};
  323. }
  324. ErrorOr<void> add_icc_data(Stream& stream, ReadonlyBytes icc_data)
  325. {
  326. // https://www.color.org/technotes/ICC-Technote-ProfileEmbedding.pdf, JFIF section
  327. constexpr StringView icc_chunk_name = "ICC_PROFILE\0"sv;
  328. // One JPEG chunk is at most 65535 bytes long, which includes the size of the 2-byte
  329. // "length" field. This leaves 65533 bytes for the actual data. One ICC chunk needs
  330. // 12 bytes for the "ICC_PROFILE\0" app id and then one byte each for the current
  331. // sequence number and the number of ICC chunks. This leaves 65519 bytes for the
  332. // ICC data.
  333. constexpr size_t icc_chunk_header_size = 2 + icc_chunk_name.length() + 1 + 1;
  334. constexpr size_t max_chunk_size = 65535 - icc_chunk_header_size;
  335. static_assert(max_chunk_size == 65519);
  336. constexpr size_t max_number_of_icc_chunks = 255; // Chunk IDs are stored in an u8 and start at 1.
  337. constexpr size_t max_icc_data_size = max_chunk_size * max_number_of_icc_chunks;
  338. // "The 1-byte chunk count limits the size of embeddable profiles to 16 707 345 bytes.""
  339. static_assert(max_icc_data_size == 16'707'345);
  340. if (icc_data.size() > max_icc_data_size)
  341. return Error::from_string_view("JPEGWriter: icc data too large for jpeg format"sv);
  342. size_t const number_of_icc_chunks = AK::ceil_div(icc_data.size(), max_chunk_size);
  343. for (size_t chunk_id = 1; chunk_id <= number_of_icc_chunks; ++chunk_id) {
  344. size_t const chunk_size = min(icc_data.size(), max_chunk_size);
  345. TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN2));
  346. TRY(stream.write_value<BigEndian<u16>>(icc_chunk_header_size + chunk_size));
  347. TRY(stream.write_until_depleted(icc_chunk_name.bytes()));
  348. TRY(stream.write_value<u8>(chunk_id));
  349. TRY(stream.write_value<u8>(number_of_icc_chunks));
  350. TRY(stream.write_until_depleted(icc_data.slice(0, chunk_size)));
  351. icc_data = icc_data.slice(chunk_size);
  352. }
  353. VERIFY(icc_data.is_empty());
  354. return {};
  355. }
  356. ErrorOr<void> add_frame_header(Stream& stream, JPEGEncodingContext const& context, IntSize size, Mode mode)
  357. {
  358. // B.2.2 - Frame header syntax
  359. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOF0));
  360. u16 const Nf = mode == Mode::CMYK ? 4 : 3;
  361. // Lf = 8 + 3 × Nf
  362. TRY(stream.write_value<BigEndian<u16>>(8 + 3 * Nf));
  363. // P
  364. TRY(stream.write_value<u8>(8));
  365. // Y
  366. TRY(stream.write_value<BigEndian<u16>>(size.height()));
  367. // X
  368. TRY(stream.write_value<BigEndian<u16>>(size.width()));
  369. // Nf
  370. TRY(stream.write_value<u8>(Nf));
  371. // Encode Nf components
  372. for (u8 i {}; i < Nf; ++i) {
  373. // Ci
  374. TRY(stream.write_value<u8>(i + 1));
  375. // Hi and Vi
  376. TRY(stream.write_value<u8>((1 << 4) | 1));
  377. // Tqi
  378. TRY(stream.write_value<u8>((i == 0 || i == 3 ? context.luminance_quantization_table() : context.chrominance_quantization_table()).id));
  379. }
  380. return {};
  381. }
  382. ErrorOr<void> add_ycck_color_transform_header(Stream& stream)
  383. {
  384. // T-REC-T.872-201206-I!!PDF-E.pdf, 6.5.3 APP14 marker segment for colour encoding
  385. TRY(stream.write_value<BigEndian<Marker>>(JPEG_APPN14));
  386. TRY(stream.write_value<BigEndian<u16>>(14));
  387. TRY(stream.write_until_depleted("Adobe\0"sv.bytes()));
  388. // These values are ignored.
  389. TRY(stream.write_value<u8>(0x64));
  390. TRY(stream.write_value<BigEndian<u16>>(0x0000));
  391. TRY(stream.write_value<BigEndian<u16>>(0x0000));
  392. // YCCK
  393. TRY(stream.write_value<u8>(0x2));
  394. return {};
  395. }
  396. ErrorOr<void> add_quantization_table(Stream& stream, QuantizationTable const& table)
  397. {
  398. // B.2.4.1 - Quantization table-specification syntax
  399. TRY(stream.write_value<BigEndian<Marker>>(JPEG_DQT));
  400. // Lq = 2 + 1 * 65
  401. TRY(stream.write_value<BigEndian<u16>>(2 + 65));
  402. // Pq and Tq
  403. TRY(stream.write_value<u8>((0 << 4) | table.id));
  404. for (u8 i = 0; i < 64; ++i)
  405. TRY(stream.write_value<u8>(table.table[zigzag_map[i]]));
  406. return {};
  407. }
  408. ErrorOr<Vector<Vector<u8>, 16>> sort_symbols_per_size(OutputHuffmanTable const& table)
  409. {
  410. // JPEG only allows symbol with a size less than or equal to 16.
  411. Vector<Vector<u8>, 16> output {};
  412. TRY(output.try_resize(16));
  413. for (auto const& symbol : table.table)
  414. TRY(output[symbol.code_length - 1].try_append(symbol.input_byte));
  415. return output;
  416. }
  417. ErrorOr<void> add_huffman_table(Stream& stream, OutputHuffmanTable const& table)
  418. {
  419. // B.2.4.2 - Huffman table-specification syntax
  420. TRY(stream.write_value<BigEndian<Marker>>(JPEG_DHT));
  421. // Lh
  422. TRY(stream.write_value<BigEndian<u16>>(2 + 17 + table.table.size()));
  423. // Tc and Th
  424. TRY(stream.write_value<u8>(table.id));
  425. auto const vectorized_table = TRY(sort_symbols_per_size(table));
  426. for (auto const& symbol_vector : vectorized_table)
  427. TRY(stream.write_value<u8>(symbol_vector.size()));
  428. for (auto const& symbol_vector : vectorized_table) {
  429. for (auto symbol : symbol_vector)
  430. TRY(stream.write_value<u8>(symbol));
  431. }
  432. return {};
  433. }
  434. ErrorOr<void> add_scan_header(Stream& stream, Mode mode)
  435. {
  436. // B.2.3 - Scan header syntax
  437. TRY(stream.write_value<BigEndian<Marker>>(JPEG_SOS));
  438. u16 const Ns = mode == Mode::CMYK ? 4 : 3;
  439. // Ls - 6 + 2 × Ns
  440. TRY(stream.write_value<BigEndian<u16>>(6 + 2 * Ns));
  441. // Ns
  442. TRY(stream.write_value<u8>(Ns));
  443. // Encode Ns components
  444. for (u8 i {}; i < Ns; ++i) {
  445. // Csj
  446. TRY(stream.write_value<u8>(i + 1));
  447. // Tdj and Taj
  448. // We're using 0 for luminance and 1 for chrominance
  449. u8 const huffman_identifier = i == 0 || i == 3 ? 0 : 1;
  450. TRY(stream.write_value<u8>((huffman_identifier << 4) | huffman_identifier));
  451. }
  452. // Ss
  453. TRY(stream.write_value<u8>(0));
  454. // Se
  455. TRY(stream.write_value<u8>(63));
  456. // Ah and Al
  457. TRY(stream.write_value<u8>((0 << 4) | 0));
  458. return {};
  459. }
  460. ErrorOr<void> add_headers(Stream& stream, JPEGEncodingContext& context, JPEGWriter::Options const& options, IntSize size, Mode mode)
  461. {
  462. context.set_luminance_quantization_table(s_default_luminance_quantization_table, options.quality);
  463. context.set_chrominance_quantization_table(s_default_chrominance_quantization_table, options.quality);
  464. context.dc_luminance_huffman_table = s_default_dc_luminance_huffman_table;
  465. context.dc_chrominance_huffman_table = s_default_dc_chrominance_huffman_table;
  466. context.ac_luminance_huffman_table = s_default_ac_luminance_huffman_table;
  467. context.ac_chrominance_huffman_table = s_default_ac_chrominance_huffman_table;
  468. TRY(add_start_of_image(stream));
  469. if (options.icc_data.has_value())
  470. TRY(add_icc_data(stream, options.icc_data.value()));
  471. if (mode == Mode::CMYK)
  472. TRY(add_ycck_color_transform_header(stream));
  473. TRY(add_frame_header(stream, context, size, mode));
  474. TRY(add_quantization_table(stream, context.luminance_quantization_table()));
  475. TRY(add_quantization_table(stream, context.chrominance_quantization_table()));
  476. TRY(add_huffman_table(stream, context.dc_luminance_huffman_table));
  477. TRY(add_huffman_table(stream, context.dc_chrominance_huffman_table));
  478. TRY(add_huffman_table(stream, context.ac_luminance_huffman_table));
  479. TRY(add_huffman_table(stream, context.ac_chrominance_huffman_table));
  480. TRY(add_scan_header(stream, mode));
  481. return {};
  482. }
  483. ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, Mode mode)
  484. {
  485. context.fdct_and_quantization(mode);
  486. TRY(context.write_huffman_stream(mode));
  487. TRY(add_end_of_image(stream));
  488. return {};
  489. }
  490. }
  491. ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap, Options const& options)
  492. {
  493. JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
  494. TRY(add_headers(stream, context, options, bitmap.size(), Mode::RGB));
  495. TRY(context.initialize_mcu(bitmap));
  496. TRY(add_image(stream, context, Mode::RGB));
  497. return {};
  498. }
  499. ErrorOr<void> JPEGWriter::encode(Stream& stream, CMYKBitmap const& bitmap, Options const& options)
  500. {
  501. JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
  502. TRY(add_headers(stream, context, options, bitmap.size(), Mode::CMYK));
  503. TRY(context.initialize_mcu(bitmap));
  504. TRY(add_image(stream, context, Mode::CMYK));
  505. return {};
  506. }
  507. }