├── doc └── img │ ├── baboon.q50.jpeg.png │ ├── baboon.q50.jpeg.crop.png │ ├── baboon.q50.knusperli.png │ └── baboon.q50.knusperli.crop.png ├── .gitignore ├── lodepng.BUILD ├── WORKSPACE ├── gamma_correct.h ├── CONTRIBUTING.md ├── idct.h ├── quantize.cc ├── quantize.h ├── dct_double.h ├── gamma_correct.cc ├── jpeg_data_decoder.h ├── preprocess_downsample.h ├── README.md ├── jpeg_data_reader.h ├── decode.cc ├── jpeg_data_decoder.cc ├── jpeg_huffman_decode.h ├── jpeg_error.h ├── idct.py ├── BUILD ├── dct_double.cc ├── jpeg_data.cc ├── jpeg_huffman_decode.cc ├── compare.py ├── idct.cc ├── output_image.h ├── jpeg_data.h ├── LICENSE ├── color_transform.h ├── preprocess_downsample.cc ├── output_image.cc └── jpeg_data_reader.cc /doc/img/baboon.q50.jpeg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/knusperli/HEAD/doc/img/baboon.q50.jpeg.png -------------------------------------------------------------------------------- /doc/img/baboon.q50.jpeg.crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/knusperli/HEAD/doc/img/baboon.q50.jpeg.crop.png -------------------------------------------------------------------------------- /doc/img/baboon.q50.knusperli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/knusperli/HEAD/doc/img/baboon.q50.knusperli.png -------------------------------------------------------------------------------- /doc/img/baboon.q50.knusperli.crop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/google/knusperli/HEAD/doc/img/baboon.q50.knusperli.crop.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Bazel build directories 2 | /bazel-bin/ 3 | /bazel-genfiles/ 4 | /bazel-knusperli/ 5 | /bazel-out/ 6 | /bazel-testlogs/ 7 | -------------------------------------------------------------------------------- /lodepng.BUILD: -------------------------------------------------------------------------------- 1 | cc_library( 2 | name = "lodepng", 3 | srcs = ["lodepng.cpp"], 4 | hdrs = ["lodepng.h"], 5 | visibility = ["//visibility:public"], 6 | ) 7 | -------------------------------------------------------------------------------- /WORKSPACE: -------------------------------------------------------------------------------- 1 | load("@bazel_tools//tools/build_defs/repo:git.bzl", "new_git_repository") 2 | 3 | new_git_repository( 4 | name = "lodepng", 5 | build_file = "@//:lodepng.BUILD", 6 | commit = "93e348f", 7 | init_submodules = False, 8 | remote = "https://github.com/lvandeve/lodepng", 9 | ) 10 | -------------------------------------------------------------------------------- /gamma_correct.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_GAMMA_CORRECT_H_ 18 | #define KNUSPERLI_GAMMA_CORRECT_H_ 19 | 20 | namespace knusperli { 21 | 22 | const double* Srgb8ToLinearTable(); 23 | 24 | } // namespace knusperli 25 | 26 | #endif // KNUSPERLI_GAMMA_CORRECT_H_ 27 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute 2 | 3 | We'd love to accept your patches and contributions to this project. There are 4 | just a few small guidelines you need to follow. 5 | 6 | ## Contributor License Agreement 7 | 8 | Contributions to this project must be accompanied by a Contributor License 9 | Agreement. You (or your employer) retain the copyright to your contribution; 10 | this simply gives us permission to use and redistribute your contributions as 11 | part of the project. Head over to to see 12 | your current agreements on file or to sign a new one. 13 | 14 | You generally only need to submit a CLA once, so if you've already submitted one 15 | (even if it was for a different project), you probably don't need to do it 16 | again. 17 | 18 | ## Code reviews 19 | 20 | All submissions, including submissions by project members, require review. We 21 | use GitHub pull requests for this purpose. Consult 22 | [GitHub Help](https://help.github.com/articles/about-pull-requests/) for more 23 | information on using pull requests. 24 | -------------------------------------------------------------------------------- /idct.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_IDCT_H_ 18 | #define KNUSPERLI_IDCT_H_ 19 | 20 | #include "jpeg_data.h" 21 | 22 | namespace knusperli { 23 | 24 | // Fills in 'result' with the inverse DCT of 'block'. 25 | // The arguments 'block' and 'result' point to 8x8 arrays that are arranged in 26 | // a row-by-row memory layout. 27 | void ComputeBlockIDCT(const coeff_t* block, uint8_t* result); 28 | 29 | } // namespace knusperli 30 | 31 | #endif // KNUSPERLI_IDCT_H_ 32 | -------------------------------------------------------------------------------- /quantize.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "quantize.h" 18 | 19 | namespace knusperli { 20 | 21 | bool QuantizeBlock(coeff_t block[kDCTBlockSize], 22 | const int q[kDCTBlockSize]) { 23 | bool changed = false; 24 | for (int k = 0; k < kDCTBlockSize; ++k) { 25 | coeff_t coeff = Quantize(block[k], q[k]); 26 | changed = changed || (coeff != block[k]); 27 | block[k] = coeff; 28 | } 29 | return changed; 30 | } 31 | 32 | } // namespace knusperli 33 | -------------------------------------------------------------------------------- /quantize.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_QUANTIZE_H_ 18 | #define KNUSPERLI_QUANTIZE_H_ 19 | 20 | #include "jpeg_data.h" 21 | 22 | namespace knusperli { 23 | 24 | inline coeff_t Quantize(coeff_t raw_coeff, int quant) { 25 | const int r = raw_coeff % quant; 26 | const coeff_t delta = 27 | 2 * r > quant ? quant - r : (-2) * r > quant ? -quant - r : -r; 28 | return raw_coeff + delta; 29 | } 30 | 31 | bool QuantizeBlock(coeff_t block[kDCTBlockSize], const int q[kDCTBlockSize]); 32 | 33 | } // namespace knusperli 34 | 35 | #endif // KNUSPERLI_QUANTIZE_H_ 36 | -------------------------------------------------------------------------------- /dct_double.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_DCT_DOUBLE_H_ 18 | #define KNUSPERLI_DCT_DOUBLE_H_ 19 | 20 | namespace knusperli { 21 | 22 | // Performs in-place floating point 8x8 DCT on block[0..63]. 23 | // Note that the DCT used here is the DCT-2 with the first term multiplied by 24 | // 1/sqrt(2) and the result scaled by 1/2. 25 | void ComputeBlockDCTDouble(double block[64]); 26 | 27 | // Performs in-place floating point 8x8 inverse DCT on block[0..63]. 28 | void ComputeBlockIDCTDouble(double block[64]); 29 | 30 | } // namespace knusperli 31 | 32 | #endif // KNUSPERLI_DCT_DOUBLE_H_ 33 | -------------------------------------------------------------------------------- /gamma_correct.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "gamma_correct.h" 18 | 19 | #include 20 | 21 | namespace knusperli { 22 | 23 | const double* NewSrgb8ToLinearTable() { 24 | double* table = new double[256]; 25 | int i = 0; 26 | for (; i < 11; ++i) { 27 | table[i] = i / 12.92; 28 | } 29 | for (; i < 256; ++i) { 30 | table[i] = 255.0 * std::pow(((i / 255.0) + 0.055) / 1.055, 2.4); 31 | } 32 | return table; 33 | } 34 | 35 | const double* Srgb8ToLinearTable() { 36 | static const double* const kSrgb8ToLinearTable = NewSrgb8ToLinearTable(); 37 | return kSrgb8ToLinearTable; 38 | } 39 | 40 | } // namespace knusperli 41 | -------------------------------------------------------------------------------- /jpeg_data_decoder.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Library to decode jpeg coefficients into an RGB image. 18 | 19 | #ifndef KNUSPERLI_JPEG_DATA_DECODER_H_ 20 | #define KNUSPERLI_JPEG_DATA_DECODER_H_ 21 | 22 | #include 23 | 24 | #include "jpeg_data.h" 25 | 26 | namespace knusperli { 27 | 28 | // Decodes the parsed jpeg coefficients into an RGB image. 29 | // There can be only either 1 or 3 image components, in either case, an RGB 30 | // output image will be generated. 31 | // Only YUV420 and YUV444 sampling factors are supported. 32 | // Vector will be empty if a decoding error occurred. 33 | std::vector DecodeJpegToRGB(const JPEGData& jpg); 34 | 35 | // Mimic libjpeg's heuristics to guess jpeg color space. 36 | // Requires that the jpg has 3 components. 37 | bool HasYCbCrColorSpace(const JPEGData& jpg); 38 | 39 | } // namespace knusperli 40 | 41 | #endif // KNUSPERLI_JPEG_DATA_DECODER_H_ 42 | -------------------------------------------------------------------------------- /preprocess_downsample.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Preprocesses U and V channel for better results after downsampling. 18 | 19 | #ifndef KNUSPERLI_PREPROCESS_DOWNSAMPLE_H_ 20 | #define KNUSPERLI_PREPROCESS_DOWNSAMPLE_H_ 21 | 22 | #include 23 | #include 24 | 25 | namespace knusperli { 26 | 27 | // Preprocesses the u (1) or v (2) channel of the given YUV image (range 0-255). 28 | std::vector> PreProcessChannel( 29 | int w, int h, int channel, float sigma, float amount, bool blur, 30 | bool sharpen, const std::vector>& image); 31 | 32 | // Gamma-compensated chroma subsampling. 33 | // Returns Y, U, V image planes, each with width x height dimensions, but the 34 | // U and V planes are composed of 2x2 blocks with the same values. 35 | std::vector > RGBToYUV420( 36 | const std::vector& rgb_in, const int width, const int height); 37 | 38 | } // namespace knusperli 39 | 40 | #endif // KNUSPERLI_PREPROCESS_DOWNSAMPLE_H_ 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Knusperli 2 | 3 | The goal of Knusperli is to reduce blocking artifacts in decoded JPEG images, by 4 | interpreting quantized DCT coefficients in the image data as an interval, rather 5 | than a fixed value, and choosing the value from that interval that minimizes 6 | discontinuities at block boundaries. 7 | 8 | a traditional JPEG decoder (Imagemagick 7.0.8-59) | Knusperli 9 | --------------------------------------------------|----------------------------------------------- 10 | ![baboon JPEG, zoomed][baboon-jpeg-crop] | ![baboon Knusperli, zoomed][baboon-knus-crop] 11 | ![baboon JPEG][baboon-jpeg] | ![baboon Knusperli][baboon-knus] 12 | 13 | ## Building 14 | 15 | Knusperli builds with [Bazel][bazel]: 16 | 17 | CC=gcc bazel build :knusperli 18 | bazel-bin/knusperli input.jpg output.png 19 | 20 | ## Details 21 | 22 | A JPEG encoder quantizes DCT coefficients by rounding coefficients to the 23 | nearest multiple of the elements of the quantization matrix. For every 24 | coefficient, there is an interval of values that would round to the same 25 | multiple. A traditional decoder uses the center of this interval to reconstruct 26 | the image. Knusperli instead chooses the value in the interval that reduces 27 | discontinuities at block boundaries. The coefficients that Knusperli uses, would 28 | have rounded to the same values that are stored in the JPEG image. 29 | 30 | ## Disclaimer 31 | 32 | This is not an officially supported Google product. 33 | 34 | [bazel]: https://bazel.build/ 35 | [baboon-jpeg-crop]: doc/img/baboon.q50.jpeg.crop.png 36 | [baboon-knus-crop]: doc/img/baboon.q50.knusperli.crop.png 37 | [baboon-jpeg]: doc/img/baboon.q50.jpeg.png 38 | [baboon-knus]: doc/img/baboon.q50.knusperli.png 39 | -------------------------------------------------------------------------------- /jpeg_data_reader.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Functions for reading a jpeg byte stream into a JPEGData object. 18 | 19 | #ifndef KNUSPERLI_JPEG_DATA_READER_H_ 20 | #define KNUSPERLI_JPEG_DATA_READER_H_ 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | 27 | #include "jpeg_data.h" 28 | 29 | namespace knusperli { 30 | 31 | enum JpegReadMode { 32 | JPEG_READ_HEADER, // only basic headers 33 | JPEG_READ_TABLES, // headers and tables (quant, Huffman, ...) 34 | JPEG_READ_ALL, // everything 35 | }; 36 | 37 | // Parses the jpeg stream contained in data[*pos ... len) and fills in *jpg with 38 | // the parsed information. 39 | // If mode is JPEG_READ_HEADER, it fills in only the image dimensions in *jpg. 40 | // Returns false if the data is not valid jpeg, or if it contains an unsupported 41 | // jpeg feature. 42 | bool ReadJpeg(const uint8_t* data, const size_t len, JpegReadMode mode, 43 | JPEGData* jpg); 44 | // string variant 45 | bool ReadJpeg(const std::string& data, JpegReadMode mode, 46 | JPEGData* jpg); 47 | 48 | } // namespace knusperli 49 | 50 | #endif // KNUSPERLI_JPEG_DATA_READER_H_ 51 | -------------------------------------------------------------------------------- /decode.cc: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Google LLC 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | #include "lodepng.h" 20 | #include "jpeg_data.h" 21 | #include "jpeg_data_decoder.h" 22 | #include "jpeg_data_reader.h" 23 | 24 | using knusperli::DecodeJpegToRGB; 25 | using knusperli::JPEGData; 26 | using knusperli::JPEG_READ_ALL; 27 | using knusperli::ReadJpeg; 28 | 29 | int main(int argc, char** argv) { 30 | if (argc != 3) { 31 | printf("Usage: knusperli \n"); 32 | return 1; 33 | } 34 | 35 | std::ifstream in_file(argv[1]); 36 | if (!in_file.good()) { 37 | printf("Failed to open input file.\n"); 38 | return 1; 39 | } 40 | std::stringstream in_data; 41 | in_data << in_file.rdbuf(); 42 | 43 | JPEGData jpg; 44 | std::vector rgb; 45 | 46 | bool read_ok = ReadJpeg(in_data.str(), JPEG_READ_ALL, &jpg); 47 | if (!read_ok) { 48 | printf("Error reading jpeg data from input file.\n"); 49 | return 1; 50 | } 51 | rgb = DecodeJpegToRGB(jpg); 52 | if (rgb.empty()) { 53 | printf("Failed to decode.\n"); 54 | return 1; 55 | } 56 | 57 | unsigned int write_error = 58 | lodepng_encode24_file(argv[2], &rgb[0], jpg.width, jpg.height); 59 | if (write_error != 0) { 60 | printf("Failed to write png.\n"); 61 | return 1; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /jpeg_data_decoder.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "jpeg_data_decoder.h" 18 | 19 | #include "output_image.h" 20 | 21 | namespace knusperli { 22 | 23 | // Mimic libjpeg's heuristics to guess jpeg color space. 24 | // Requires that the jpg has 3 components. 25 | bool HasYCbCrColorSpace(const JPEGData& jpg) { 26 | bool has_Adobe_marker = false; 27 | uint8_t Adobe_transform = 0; 28 | for (const std::string& app : jpg.app_data) { 29 | if (static_cast(app[0]) == 0xe0) { 30 | return true; 31 | } else if (static_cast(app[0]) == 0xee && app.size() >= 15) { 32 | has_Adobe_marker = true; 33 | Adobe_transform = app[14]; 34 | } 35 | } 36 | if (has_Adobe_marker) { 37 | return (Adobe_transform != 0); 38 | } 39 | const int cid0 = jpg.components[0].id; 40 | const int cid1 = jpg.components[1].id; 41 | const int cid2 = jpg.components[2].id; 42 | return (cid0 != 'R' || cid1 != 'G' || cid2 != 'B'); 43 | } 44 | 45 | std::vector DecodeJpegToRGB(const JPEGData& jpg) { 46 | if (jpg.components.size() == 1 || 47 | (jpg.components.size() == 3 && 48 | HasYCbCrColorSpace(jpg) && (jpg.Is420() || jpg.Is444()))) { 49 | OutputImage img(jpg.width, jpg.height); 50 | img.CopyFromJpegData(jpg); 51 | return img.ToSRGB(); 52 | } 53 | return std::vector(); 54 | } 55 | 56 | } // namespace knusperli 57 | -------------------------------------------------------------------------------- /jpeg_huffman_decode.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Utility function for building a Huffman lookup table for the jpeg decoder. 18 | 19 | #ifndef KNUSPERLI_JPEG_HUFFMAN_DECODE_H_ 20 | #define KNUSPERLI_JPEG_HUFFMAN_DECODE_H_ 21 | 22 | #include 23 | 24 | namespace knusperli { 25 | 26 | static const int kJpegHuffmanRootTableBits = 8; 27 | // Maximum huffman lookup table size. 28 | // According to zlib/examples/enough.c, 758 entries are always enough for 29 | // an alphabet of 257 symbols (256 + 1 special symbol for the all 1s code) and 30 | // max bit length 16 if the root table has 8 bits. 31 | static const int kJpegHuffmanLutSize = 758; 32 | 33 | struct HuffmanTableEntry { 34 | // Initialize the value to an invalid symbol so that we can recognize it 35 | // when reading the bit stream using a Huffman code with space > 0. 36 | HuffmanTableEntry() : bits(0), value(0xffff) {} 37 | 38 | uint8_t bits; // number of bits used for this symbol 39 | uint16_t value; // symbol value or table offset 40 | }; 41 | 42 | // Builds jpeg-style Huffman lookup table from the given symbols. 43 | // The symbols are in order of increasing bit lengths. The number of symbols 44 | // with bit length n is given in counts[n] for each n >= 1. 45 | // Returns the size of the lookup table. 46 | int BuildJpegHuffmanTable(const int* counts, const int* symbols, 47 | HuffmanTableEntry* lut); 48 | 49 | } // namespace knusperli 50 | 51 | #endif // KNUSPERLI_JPEG_HUFFMAN_DECODE_H_ 52 | -------------------------------------------------------------------------------- /jpeg_error.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Definition of error codes for parsing jpeg files. 18 | 19 | #ifndef KNUSPERLI_JPEG_ERROR_H_ 20 | #define KNUSPERLI_JPEG_ERROR_H_ 21 | 22 | namespace knusperli { 23 | 24 | enum JPEGReadError { 25 | JPEG_OK = 0, 26 | JPEG_SOI_NOT_FOUND, 27 | JPEG_SOF_NOT_FOUND, 28 | JPEG_UNEXPECTED_EOF, 29 | JPEG_MARKER_BYTE_NOT_FOUND, 30 | JPEG_UNSUPPORTED_MARKER, 31 | JPEG_WRONG_MARKER_SIZE, 32 | JPEG_INVALID_PRECISION, 33 | JPEG_INVALID_WIDTH, 34 | JPEG_INVALID_HEIGHT, 35 | JPEG_INVALID_NUMCOMP, 36 | JPEG_INVALID_SAMP_FACTOR, 37 | JPEG_INVALID_START_OF_SCAN, 38 | JPEG_INVALID_END_OF_SCAN, 39 | JPEG_INVALID_SCAN_BIT_POSITION, 40 | JPEG_INVALID_COMPS_IN_SCAN, 41 | JPEG_INVALID_HUFFMAN_INDEX, 42 | JPEG_INVALID_QUANT_TBL_INDEX, 43 | JPEG_INVALID_QUANT_VAL, 44 | JPEG_INVALID_MARKER_LEN, 45 | JPEG_INVALID_SAMPLING_FACTORS, 46 | JPEG_INVALID_HUFFMAN_CODE, 47 | JPEG_INVALID_SYMBOL, 48 | JPEG_NON_REPRESENTABLE_DC_COEFF, 49 | JPEG_NON_REPRESENTABLE_AC_COEFF, 50 | JPEG_INVALID_SCAN, 51 | JPEG_OVERLAPPING_SCANS, 52 | JPEG_INVALID_SCAN_ORDER, 53 | JPEG_EXTRA_ZERO_RUN, 54 | JPEG_DUPLICATE_DRI, 55 | JPEG_DUPLICATE_SOF, 56 | JPEG_WRONG_RESTART_MARKER, 57 | JPEG_DUPLICATE_COMPONENT_ID, 58 | JPEG_COMPONENT_NOT_FOUND, 59 | JPEG_HUFFMAN_TABLE_NOT_FOUND, 60 | JPEG_HUFFMAN_TABLE_ERROR, 61 | JPEG_QUANT_TABLE_NOT_FOUND, 62 | JPEG_EMPTY_DHT, 63 | JPEG_EMPTY_DQT, 64 | JPEG_OUT_OF_BAND_COEFF, 65 | JPEG_EOB_RUN_TOO_LONG, 66 | JPEG_IMAGE_TOO_LARGE, 67 | }; 68 | 69 | } // namespace knusperli 70 | 71 | #endif // KNUSPERLI_JPEG_ERROR_H_ 72 | -------------------------------------------------------------------------------- /idct.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import math 18 | 19 | cos = math.cos 20 | pi = math.sqrt 21 | sqrt = math.sqrt 22 | 23 | 24 | def alpha(u): 25 | return sqrt(0.5) if u == 0 else 1.0 26 | 27 | 28 | def idct(x, u): 29 | # Compute the coefficient in its normal range first, then convert to 13-bit 30 | # fixed-point arithmetic. 31 | res_float = alpha(u) * cos((2 * x + 1) * u * pi / 16.0) * sqrt(2) 32 | return int(res_float * (1 << 13)) 33 | 34 | 35 | def dct(x, u): 36 | res_float = alpha(u) * cos((2 * x + 1) * u * pi / 16.0) * sqrt(2) 37 | 38 | 39 | for x in range(8): 40 | coefs = ['{:8}'.format(idct(x, u)) for u in range(8)] 41 | print(', '.join(coefs + [''])) 42 | 43 | print('\n----------\n') 44 | 45 | coefs_left = ['{:8}'.format(idct(-0.5, u)) for u in range(8)] 46 | print(', '.join(coefs_left + [''])) 47 | 48 | coefs = ['{:8}'.format(idct(-0.5, u)) for u in range(8)] 49 | print(', '.join(coefs + [''])) 50 | 51 | print('sqrt2 in 9-bits behind the point: ', int(sqrt(2) * 512)) 52 | print('half sqrt2 in 9-bits behind the point: ', int(sqrt(0.5) * 512)) 53 | 54 | print('\n----------\nDCT of a linear gradient (10-bit precision):') 55 | 56 | 57 | def dct(f, u): 58 | """Computes the (u, 0) DCT coefficient, given the pixel values f(x, 0). 59 | """ 60 | s = sum(cos((2 * x + 1) * u * pi / 16.0) * f(x, 0) for x in range(8)) 61 | return 0.25 * alpha(u) * alpha(0) * s 62 | 63 | 64 | def linear_gradient(x, y): 65 | """A horizontal linear gradient from 0.0 to 1.0 on the interval [-0.5, 7.5]. 66 | 67 | This has gamma correction applied. 68 | """ 69 | return ((x + 0.5) / 8.0)**2.2 70 | 71 | 72 | coefs = [int((1 << 10) * dct(linear_gradient, u)) for u in range(8)] 73 | print(', '.join('{:8}'.format(c) for c in coefs)) 74 | -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | package( 2 | default_visibility = ["//visibility:public"], 3 | ) 4 | 5 | cc_binary( 6 | name = "knusperli", 7 | srcs = [ 8 | "decode.cc", 9 | ], 10 | deps = [ 11 | ":jpeg_data", 12 | ":jpeg_data_decoder", 13 | ":jpeg_data_reader", 14 | ":output_image", 15 | "@lodepng", 16 | ], 17 | ) 18 | 19 | cc_library( 20 | name = "jpeg_data_reader", 21 | srcs = [ 22 | "jpeg_data_reader.cc", 23 | "jpeg_huffman_decode.cc", 24 | "jpeg_huffman_decode.h", 25 | ], 26 | hdrs = ["jpeg_data_reader.h"], 27 | deps = [ 28 | ":jpeg_data", 29 | ], 30 | ) 31 | 32 | cc_library( 33 | name = "jpeg_data_decoder", 34 | srcs = ["jpeg_data_decoder.cc"], 35 | hdrs = ["jpeg_data_decoder.h"], 36 | deps = [ 37 | ":jpeg_data", 38 | ":output_image", 39 | ], 40 | ) 41 | 42 | cc_library( 43 | name = "jpeg_data", 44 | srcs = ["jpeg_data.cc"], 45 | hdrs = [ 46 | "jpeg_data.h", 47 | "jpeg_error.h", 48 | ], 49 | ) 50 | 51 | cc_library( 52 | name = "output_image", 53 | srcs = ["output_image.cc"], 54 | hdrs = ["output_image.h"], 55 | deps = [ 56 | ":color_transform", 57 | ":dct_double", 58 | ":gamma_correct", 59 | ":idct", 60 | ":jpeg_data", 61 | ":preprocess_downsample", 62 | ":quantize", 63 | ], 64 | ) 65 | 66 | cc_library( 67 | name = "color_transform", 68 | textual_hdrs = ["color_transform.h"], 69 | ) 70 | 71 | cc_library( 72 | name = "dct_double", 73 | srcs = ["dct_double.cc"], 74 | hdrs = ["dct_double.h"], 75 | copts = ["-ffast-math"], 76 | ) 77 | 78 | cc_library( 79 | name = "gamma_correct", 80 | srcs = ["gamma_correct.cc"], 81 | hdrs = ["gamma_correct.h"], 82 | ) 83 | 84 | cc_library( 85 | name = "idct", 86 | srcs = ["idct.cc"], 87 | hdrs = ["idct.h"], 88 | deps = [":jpeg_data"], 89 | ) 90 | 91 | cc_library( 92 | name = "preprocess_downsample", 93 | srcs = ["preprocess_downsample.cc"], 94 | hdrs = ["preprocess_downsample.h"], 95 | ) 96 | 97 | cc_library( 98 | name = "quantize", 99 | srcs = ["quantize.cc"], 100 | hdrs = ["quantize.h"], 101 | deps = [":jpeg_data"], 102 | ) 103 | -------------------------------------------------------------------------------- /dct_double.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "dct_double.h" 18 | 19 | #include 20 | 21 | namespace knusperli { 22 | 23 | namespace { 24 | 25 | // kDCTMatrix[8*u+x] = 0.5*alpha(u)*cos((2*x+1)*u*M_PI/16), 26 | // where alpha(0) = 1/sqrt(2) and alpha(u) = 1 for u > 0. 27 | static const double kDCTMatrix[64] = { 28 | 0.3535533906, 0.3535533906, 0.3535533906, 0.3535533906, 29 | 0.3535533906, 0.3535533906, 0.3535533906, 0.3535533906, 30 | 0.4903926402, 0.4157348062, 0.2777851165, 0.0975451610, 31 | -0.0975451610, -0.2777851165, -0.4157348062, -0.4903926402, 32 | 0.4619397663, 0.1913417162, -0.1913417162, -0.4619397663, 33 | -0.4619397663, -0.1913417162, 0.1913417162, 0.4619397663, 34 | 0.4157348062, -0.0975451610, -0.4903926402, -0.2777851165, 35 | 0.2777851165, 0.4903926402, 0.0975451610, -0.4157348062, 36 | 0.3535533906, -0.3535533906, -0.3535533906, 0.3535533906, 37 | 0.3535533906, -0.3535533906, -0.3535533906, 0.3535533906, 38 | 0.2777851165, -0.4903926402, 0.0975451610, 0.4157348062, 39 | -0.4157348062, -0.0975451610, 0.4903926402, -0.2777851165, 40 | 0.1913417162, -0.4619397663, 0.4619397663, -0.1913417162, 41 | -0.1913417162, 0.4619397663, -0.4619397663, 0.1913417162, 42 | 0.0975451610, -0.2777851165, 0.4157348062, -0.4903926402, 43 | 0.4903926402, -0.4157348062, 0.2777851165, -0.0975451610, 44 | }; 45 | 46 | void DCT1d(const double* in, int stride, double* out) { 47 | for (int x = 0; x < 8; ++x) { 48 | out[x * stride] = 0.0; 49 | for (int u = 0; u < 8; ++u) { 50 | out[x * stride] += kDCTMatrix[8 * x + u] * in[u * stride]; 51 | } 52 | } 53 | } 54 | 55 | void IDCT1d(const double* in, int stride, double* out) { 56 | for (int x = 0; x < 8; ++x) { 57 | out[x * stride] = 0.0; 58 | for (int u = 0; u < 8; ++u) { 59 | out[x * stride] += kDCTMatrix[8 * u + x] * in[u * stride]; 60 | } 61 | } 62 | } 63 | 64 | typedef void (*Transform1d)(const double* in, int stride, double* out); 65 | 66 | void TransformBlock(double block[64], Transform1d f) { 67 | double tmp[64]; 68 | for (int x = 0; x < 8; ++x) { 69 | f(&block[x], 8, &tmp[x]); 70 | } 71 | for (int y = 0; y < 8; ++y) { 72 | f(&tmp[8 * y], 1, &block[8 * y]); 73 | } 74 | } 75 | 76 | } // namespace 77 | 78 | void ComputeBlockDCTDouble(double block[64]) { 79 | TransformBlock(block, DCT1d); 80 | } 81 | 82 | void ComputeBlockIDCTDouble(double block[64]) { 83 | TransformBlock(block, IDCT1d); 84 | } 85 | 86 | } // namespace knusperli 87 | -------------------------------------------------------------------------------- /jpeg_data.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "jpeg_data.h" 18 | 19 | #include 20 | #include 21 | 22 | namespace knusperli { 23 | 24 | bool JPEGData::Is420() const { 25 | return (components.size() == 3 && 26 | max_h_samp_factor == 2 && 27 | max_v_samp_factor == 2 && 28 | components[0].h_samp_factor == 2 && 29 | components[0].v_samp_factor == 2 && 30 | components[1].h_samp_factor == 1 && 31 | components[1].v_samp_factor == 1 && 32 | components[2].h_samp_factor == 1 && 33 | components[2].v_samp_factor == 1); 34 | } 35 | 36 | bool JPEGData::Is444() const { 37 | return (components.size() == 3 && 38 | max_h_samp_factor == 1 && 39 | max_v_samp_factor == 1 && 40 | components[0].h_samp_factor == 1 && 41 | components[0].v_samp_factor == 1 && 42 | components[1].h_samp_factor == 1 && 43 | components[1].v_samp_factor == 1 && 44 | components[2].h_samp_factor == 1 && 45 | components[2].v_samp_factor == 1); 46 | } 47 | 48 | void InitJPEGDataForYUV444(int w, int h, JPEGData* jpg) { 49 | jpg->width = w; 50 | jpg->height = h; 51 | jpg->max_h_samp_factor = 1; 52 | jpg->max_v_samp_factor = 1; 53 | jpg->MCU_rows = (h + 7) >> 3; 54 | jpg->MCU_cols = (w + 7) >> 3; 55 | jpg->quant.resize(3); 56 | jpg->components.resize(3); 57 | for (int i = 0; i < 3; ++i) { 58 | JPEGComponent* c = &jpg->components[i]; 59 | c->id = i; 60 | c->h_samp_factor = 1; 61 | c->v_samp_factor = 1; 62 | c->quant_idx = i; 63 | c->width_in_blocks = jpg->MCU_cols; 64 | c->height_in_blocks = jpg->MCU_rows; 65 | c->num_blocks = c->width_in_blocks * c->height_in_blocks; 66 | c->coeffs.resize(c->num_blocks * kDCTBlockSize); 67 | } 68 | } 69 | 70 | void SaveQuantTables(const int q[3][kDCTBlockSize], JPEGData* jpg) { 71 | const size_t kTableSize = kDCTBlockSize * sizeof(q[0][0]); 72 | jpg->quant.clear(); 73 | int num_tables = 0; 74 | for (int i = 0; i < jpg->components.size(); ++i) { 75 | JPEGComponent* comp = &jpg->components[i]; 76 | // Check if we have this quant table already. 77 | bool found = false; 78 | for (int j = 0; j < num_tables; ++j) { 79 | if (memcmp(&q[i][0], &jpg->quant[j].values[0], kTableSize) == 0) { 80 | comp->quant_idx = j; 81 | found = true; 82 | break; 83 | } 84 | } 85 | if (!found) { 86 | JPEGQuantTable table; 87 | memcpy(&table.values[0], &q[i][0], kTableSize); 88 | table.precision = 0; 89 | for (int k = 0; k < kDCTBlockSize; ++k) { 90 | assert(table.values[k] >= 0); 91 | assert(table.values[k] < (1 << 16)); 92 | if (table.values[k] > 0xff) { 93 | table.precision = 1; 94 | } 95 | } 96 | table.index = num_tables; 97 | comp->quant_idx = num_tables; 98 | jpg->quant.push_back(table); 99 | ++num_tables; 100 | } 101 | } 102 | } 103 | 104 | } // namespace knusperli 105 | -------------------------------------------------------------------------------- /jpeg_huffman_decode.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "jpeg_huffman_decode.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | #include "jpeg_data.h" 25 | 26 | namespace knusperli { 27 | 28 | // Returns the table width of the next 2nd level table, count is the histogram 29 | // of bit lengths for the remaining symbols, len is the code length of the next 30 | // processed symbol. 31 | static inline int NextTableBitSize(const int* count, int len) { 32 | int left = 1 << (len - kJpegHuffmanRootTableBits); 33 | while (len < kJpegHuffmanMaxBitLength) { 34 | left -= count[len]; 35 | if (left <= 0) break; 36 | ++len; 37 | left <<= 1; 38 | } 39 | return len - kJpegHuffmanRootTableBits; 40 | } 41 | 42 | int BuildJpegHuffmanTable(const int* count_in, const int* symbols, 43 | HuffmanTableEntry* lut) { 44 | HuffmanTableEntry code; // current table entry 45 | HuffmanTableEntry* table; // next available space in table 46 | int len; // current code length 47 | int idx; // symbol index 48 | int key; // prefix code 49 | int reps; // number of replicate key values in current table 50 | int low; // low bits for current root entry 51 | int table_bits; // key length of current table 52 | int table_size; // size of current table 53 | int total_size; // sum of root table size and 2nd level table sizes 54 | 55 | // Make a local copy of the input bit length histogram. 56 | int count[kJpegHuffmanMaxBitLength + 1] = { 0 }; 57 | int total_count = 0; 58 | for (len = 1; len <= kJpegHuffmanMaxBitLength; ++len) { 59 | count[len] = count_in[len]; 60 | total_count += count[len]; 61 | } 62 | 63 | table = lut; 64 | table_bits = kJpegHuffmanRootTableBits; 65 | table_size = 1 << table_bits; 66 | total_size = table_size; 67 | 68 | // Special case code with only one value. 69 | if (total_count == 1) { 70 | code.bits = 0; 71 | code.value = symbols[0]; 72 | for (key = 0; key < total_size; ++key) { 73 | table[key] = code; 74 | } 75 | return total_size; 76 | } 77 | 78 | // Fill in root table. 79 | key = 0; 80 | idx = 0; 81 | for (len = 1; len <= kJpegHuffmanRootTableBits; ++len) { 82 | for (; count[len] > 0; --count[len]) { 83 | code.bits = len; 84 | code.value = symbols[idx++]; 85 | reps = 1 << (kJpegHuffmanRootTableBits - len); 86 | while (reps--) { 87 | table[key++] = code; 88 | } 89 | } 90 | } 91 | 92 | // Fill in 2nd level tables and add pointers to root table. 93 | table += table_size; 94 | table_size = 0; 95 | low = 0; 96 | for (len = kJpegHuffmanRootTableBits + 1; 97 | len <= kJpegHuffmanMaxBitLength; ++len) { 98 | for (; count[len] > 0; --count[len]) { 99 | // Start a new sub-table if the previous one is full. 100 | if (low >= table_size) { 101 | table += table_size; 102 | table_bits = NextTableBitSize(count, len); 103 | table_size = 1 << table_bits; 104 | total_size += table_size; 105 | low = 0; 106 | lut[key].bits = table_bits + kJpegHuffmanRootTableBits; 107 | lut[key].value = (table - lut) - key; 108 | ++key; 109 | } 110 | code.bits = len - kJpegHuffmanRootTableBits; 111 | code.value = symbols[idx++]; 112 | reps = 1 << (table_bits - code.bits); 113 | while (reps--) { 114 | table[low++] = code; 115 | } 116 | } 117 | } 118 | 119 | return total_size; 120 | } 121 | 122 | } // namespace knusperli 123 | -------------------------------------------------------------------------------- /compare.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Copyright 2018 Google LLC 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | 17 | import os 18 | import sys 19 | import subprocess 20 | 21 | cjpeg_dirs = ('cjpeg_30', 'cjpeg_70', 'cjpeg_78', 'cjpeg_90', 'cjpeg_95') 22 | 23 | 24 | def iterate_pngs(directory): 25 | for root, dirs, files in os.walk(directory, followlinks=True): 26 | for f in files: 27 | if f.endswith('.png'): 28 | yield os.path.join(root, f) 29 | 30 | 31 | def create_output_dirs(directory): 32 | for cdir in cjpeg_dirs: 33 | try: 34 | os.makedirs(os.path.join(directory, cdir)) 35 | except: 36 | pass 37 | 38 | 39 | def compress(cjpeg, src_file, dst_file, quality): 40 | cmdline = [cjpeg, '-quality', str(quality), src_file] 41 | jpeg_data = subprocess.check_output([x.encode('utf-8') for x in cmdline]) 42 | 43 | with open(dst_file, 'wb') as f: 44 | f.write(jpeg_data) 45 | 46 | 47 | def decompress(knusperli, src_file, dst_file): 48 | cmdline = [knusperli, src_file, dst_file] 49 | return subprocess.Popen( 50 | [x.encode('utf-8') for x in cmdline], stdout=subprocess.DEVNULL) 51 | 52 | 53 | def compare(butteraugli, orig_file, compressed_file): 54 | """Runs Butteraugli to compare the original and compressed file.""" 55 | cmdline = [butteraugli, orig_file, compressed_file, compressed_file + '.ppm'] 56 | return subprocess.Popen( 57 | [x.encode('utf-8') for x in cmdline], stdout=subprocess.PIPE) 58 | 59 | 60 | def print_stats(label, values): 61 | values = sorted(values) 62 | median = ( 63 | values[int(len(values) / 2)] + values[int(len(values) / 2) - 1]) / 2.0 64 | print('{} min: {:.3}, median: {:.3}, max: {:.3}'.format( 65 | label, min(values), median, max(values))) 66 | 67 | 68 | def main(): 69 | """Usage: ./compare.py input_directory output_directory 70 | 71 | The program expects the following programs to exist on the PATH: 72 | 73 | cjpeg 74 | knusperli 75 | butteraugli 76 | 77 | All of these can be overridden with the environment variables CJPEG, 78 | KNUSPERLI, and BUTTERAUGLI. 79 | """ 80 | 81 | if len(sys.argv) != 3: 82 | sys.exit(main.__doc__) 83 | 84 | input_dir = sys.argv[1] 85 | output_dir = sys.argv[2] 86 | cjpeg = os.getenv('CJPEG', 'cjpeg') 87 | knusperli = os.getenv('KNUSPERLI', 'knusperli') 88 | butteraugli = os.getenv('BUTTERAUGLI', 'butteraugli') 89 | 90 | create_output_dirs(output_dir) 91 | 92 | # Run all the decompress and compare programs concurrently, and store the 93 | # process handle (Popen) in a list so we can wait for all to complete. 94 | procs_jpeg = [] 95 | procs_knus = [] 96 | 97 | for input_png in iterate_pngs(input_dir): 98 | print(input_png) 99 | input_png_basename = os.path.basename(input_png) 100 | fnames = [ 101 | os.path.join(output_dir, dirname, input_png_basename) 102 | for dirname in cjpeg_dirs 103 | ] 104 | jpg_fnames = [os.path.splitext(fname)[0] + '.jpg' for fname in fnames] 105 | # Note: for a quality of 80, mozjpeg produces an RGB file (rather than a 106 | # YCbCr file), which Knusperli does not handle, so pick 78 instead. 107 | for i, quality in enumerate((30, 70, 78, 90, 95)): 108 | compress(cjpeg, input_png, jpg_fnames[i], quality) 109 | 110 | for i in range(len(fnames)): 111 | proc_decompress = decompress(knusperli, jpg_fnames[i], fnames[i]) 112 | procs_jpeg.append(compare(butteraugli, input_png, jpg_fnames[i])) 113 | # Wait untill decompression is complete before we can compare its output. 114 | proc_decompress.wait() 115 | procs_knus.append(compare(butteraugli, input_png, fnames[i])) 116 | 117 | # Wait for all processes to complete and collect the Butteraugli scores. 118 | scores_jpeg = [] 119 | scores_knus = [] 120 | 121 | for proc in procs_jpeg: 122 | proc.wait() 123 | scores_jpeg.append(float(proc.stdout.read())) 124 | 125 | for proc in procs_knus: 126 | proc.wait() 127 | scores_knus.append(float(proc.stdout.read())) 128 | 129 | scores_diff = [jpeg - knus for jpeg, knus in zip(scores_jpeg, scores_knus)] 130 | 131 | print_stats('Jpeg scores: ', scores_jpeg) 132 | print_stats('Knusperli scores: ', scores_knus) 133 | print_stats('Jpeg - Knusperli: ', scores_diff) 134 | 135 | 136 | if __name__ == '__main__': 137 | main() 138 | -------------------------------------------------------------------------------- /idct.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Integer implementation of the Inverse Discrete Cosine Transform (IDCT). 18 | 19 | #include "idct.h" 20 | 21 | #include 22 | 23 | namespace knusperli { 24 | 25 | // kIDCTMatrix[8*x+u] = alpha(u)*cos((2*x+1)*u*M_PI/16)*sqrt(2), with fixed 13 26 | // bit precision, where alpha(0) = 1/sqrt(2) and alpha(u) = 1 for u > 0. 27 | // Some coefficients are off by +-1 to mimick libjpeg's behaviour. 28 | static const int kIDCTMatrix[kDCTBlockSize] = { 29 | 8192, 11363, 10703, 9633, 8192, 6437, 4433, 2260, 30 | 8192, 9633, 4433, -2259, -8192, -11362, -10704, -6436, 31 | 8192, 6437, -4433, -11362, -8192, 2261, 10704, 9633, 32 | 8192, 2260, -10703, -6436, 8192, 9633, -4433, -11363, 33 | 8192, -2260, -10703, 6436, 8192, -9633, -4433, 11363, 34 | 8192, -6437, -4433, 11362, -8192, -2261, 10704, -9633, 35 | 8192, -9633, 4433, 2259, -8192, 11362, -10704, 6436, 36 | 8192, -11363, 10703, -9633, 8192, -6437, 4433, -2260, 37 | }; 38 | 39 | // Computes out[x] = sum{kIDCTMatrix[8*x+u]*in[u*stride]; for u in [0..7]} 40 | inline void Compute1dIDCT(const coeff_t* in, const int stride, int out[8]) { 41 | int tmp0, tmp1, tmp2, tmp3, tmp4; 42 | 43 | tmp1 = kIDCTMatrix[0] * in[0]; 44 | out[0] = out[1] = out[2] = out[3] = out[4] = out[5] = out[6] = out[7] = tmp1; 45 | 46 | tmp0 = in[stride]; 47 | tmp1 = kIDCTMatrix[ 1] * tmp0; 48 | tmp2 = kIDCTMatrix[ 9] * tmp0; 49 | tmp3 = kIDCTMatrix[17] * tmp0; 50 | tmp4 = kIDCTMatrix[25] * tmp0; 51 | out[0] += tmp1; 52 | out[1] += tmp2; 53 | out[2] += tmp3; 54 | out[3] += tmp4; 55 | out[4] -= tmp4; 56 | out[5] -= tmp3; 57 | out[6] -= tmp2; 58 | out[7] -= tmp1; 59 | 60 | tmp0 = in[2 * stride]; 61 | tmp1 = kIDCTMatrix[ 2] * tmp0; 62 | tmp2 = kIDCTMatrix[10] * tmp0; 63 | out[0] += tmp1; 64 | out[1] += tmp2; 65 | out[2] -= tmp2; 66 | out[3] -= tmp1; 67 | out[4] -= tmp1; 68 | out[5] -= tmp2; 69 | out[6] += tmp2; 70 | out[7] += tmp1; 71 | 72 | tmp0 = in[3 * stride]; 73 | tmp1 = kIDCTMatrix[ 3] * tmp0; 74 | tmp2 = kIDCTMatrix[11] * tmp0; 75 | tmp3 = kIDCTMatrix[19] * tmp0; 76 | tmp4 = kIDCTMatrix[27] * tmp0; 77 | out[0] += tmp1; 78 | out[1] += tmp2; 79 | out[2] += tmp3; 80 | out[3] += tmp4; 81 | out[4] -= tmp4; 82 | out[5] -= tmp3; 83 | out[6] -= tmp2; 84 | out[7] -= tmp1; 85 | 86 | tmp0 = in[4 * stride]; 87 | tmp1 = kIDCTMatrix[ 4] * tmp0; 88 | out[0] += tmp1; 89 | out[1] -= tmp1; 90 | out[2] -= tmp1; 91 | out[3] += tmp1; 92 | out[4] += tmp1; 93 | out[5] -= tmp1; 94 | out[6] -= tmp1; 95 | out[7] += tmp1; 96 | 97 | tmp0 = in[5 * stride]; 98 | tmp1 = kIDCTMatrix[ 5] * tmp0; 99 | tmp2 = kIDCTMatrix[13] * tmp0; 100 | tmp3 = kIDCTMatrix[21] * tmp0; 101 | tmp4 = kIDCTMatrix[29] * tmp0; 102 | out[0] += tmp1; 103 | out[1] += tmp2; 104 | out[2] += tmp3; 105 | out[3] += tmp4; 106 | out[4] -= tmp4; 107 | out[5] -= tmp3; 108 | out[6] -= tmp2; 109 | out[7] -= tmp1; 110 | 111 | tmp0 = in[6 * stride]; 112 | tmp1 = kIDCTMatrix[ 6] * tmp0; 113 | tmp2 = kIDCTMatrix[14] * tmp0; 114 | out[0] += tmp1; 115 | out[1] += tmp2; 116 | out[2] -= tmp2; 117 | out[3] -= tmp1; 118 | out[4] -= tmp1; 119 | out[5] -= tmp2; 120 | out[6] += tmp2; 121 | out[7] += tmp1; 122 | 123 | tmp0 = in[7 * stride]; 124 | tmp1 = kIDCTMatrix[ 7] * tmp0; 125 | tmp2 = kIDCTMatrix[15] * tmp0; 126 | tmp3 = kIDCTMatrix[23] * tmp0; 127 | tmp4 = kIDCTMatrix[31] * tmp0; 128 | out[0] += tmp1; 129 | out[1] += tmp2; 130 | out[2] += tmp3; 131 | out[3] += tmp4; 132 | out[4] -= tmp4; 133 | out[5] -= tmp3; 134 | out[6] -= tmp2; 135 | out[7] -= tmp1; 136 | } 137 | 138 | void ComputeBlockIDCT(const coeff_t* block, uint8_t* out) { 139 | coeff_t colidcts[kDCTBlockSize]; 140 | const int kColScale = 11; 141 | const int kColRound = 1 << (kColScale - 1); 142 | for (int x = 0; x < 8; ++x) { 143 | int colbuf[8] = { 0 }; 144 | Compute1dIDCT(&block[x], 8, colbuf); 145 | for (int y = 0; y < 8; ++y) { 146 | colidcts[8 * y + x] = (colbuf[y] + kColRound) >> kColScale; 147 | } 148 | } 149 | const int kRowScale = 18; 150 | const int kRowRound = 257 << (kRowScale - 1); // includes offset by 128 151 | for (int y = 0; y < 8; ++y) { 152 | const int rowidx = 8 * y; 153 | int rowbuf[8] = { 0 }; 154 | Compute1dIDCT(&colidcts[rowidx], 1, rowbuf); 155 | for (int x = 0; x < 8; ++x) { 156 | out[rowidx + x] = 157 | std::max(0, std::min(255, (rowbuf[x] + kRowRound) >> kRowScale)); 158 | } 159 | } 160 | } 161 | 162 | } // namespace knusperli 163 | -------------------------------------------------------------------------------- /output_image.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_OUTPUT_IMAGE_H_ 18 | #define KNUSPERLI_OUTPUT_IMAGE_H_ 19 | 20 | #include 21 | #include 22 | 23 | #include "jpeg_data.h" 24 | 25 | namespace knusperli { 26 | 27 | class OutputImageComponent { 28 | public: 29 | OutputImageComponent(int w, int h); 30 | 31 | void Reset(int factor_x, int factor_y); 32 | 33 | int width() const { return width_; } 34 | int height() const { return height_; } 35 | int factor_x() const { return factor_x_; } 36 | int factor_y() const { return factor_y_; } 37 | int width_in_blocks() const { return width_in_blocks_; } 38 | int height_in_blocks() const { return height_in_blocks_; } 39 | const coeff_t* coeffs() const { return &coeffs_[0]; } 40 | const int* quant() const { return &quant_[0]; } 41 | bool IsAllZero() const; 42 | 43 | // Fills in block[] with the 8x8 coefficient block with block coordinates 44 | // (block_x, block_y). 45 | // NOTE: If the component is 2x2 subsampled, this corresponds to the 16x16 46 | // pixel area with upper-left corner (16 * block_x, 16 * block_y). 47 | void GetCoeffBlock(int block_x, int block_y, 48 | coeff_t block[kDCTBlockSize]) const; 49 | 50 | // Fills in out[] array with the 8-bit pixel view of this component cropped 51 | // to the specified window. The window's upper-left corner, (xmin, ymin) must 52 | // be within the image, but the window may extend past the image. In that 53 | // case the edge pixels are duplicated. 54 | void ToPixels(int xmin, int ymin, int xsize, int ysize, 55 | uint8_t* out, int stride) const; 56 | 57 | // Fills in out[] array with the floating-point precision pixel view of the 58 | // component. 59 | // REQUIRES: factor_x() == 1 and factor_y() == 1. 60 | void ToFloatPixels(float* out, int stride) const; 61 | 62 | // Sets the 8x8 coefficient block with block coordinates (block_x, block_y) 63 | // to block[]. 64 | // NOTE: If the component is 2x2 subsampled, this corresponds to the 16x16 65 | // pixel area with upper-left corner (16 * block_x, 16 * block_y). 66 | // REQUIRES: block[k] % quant()[k] == 0 for each coefficient index k. 67 | void SetCoeffBlock(int block_x, int block_y, 68 | const coeff_t block[kDCTBlockSize]); 69 | 70 | // Requires that comp is not downsampled. 71 | void CopyFromJpegComponent(const JPEGComponent& comp, 72 | int factor_x, int factor_y, 73 | const int* quant); 74 | 75 | void ApplyGlobalQuantization(const int q[kDCTBlockSize]); 76 | 77 | private: 78 | void UpdatePixelsForBlock(int block_x, int block_y, 79 | const uint8_t idct[kDCTBlockSize]); 80 | 81 | const int width_; 82 | const int height_; 83 | int factor_x_; 84 | int factor_y_; 85 | int width_in_blocks_; 86 | int height_in_blocks_; 87 | int num_blocks_; 88 | std::vector coeffs_; 89 | std::vector pixels_; 90 | // Same as last argument of ApplyGlobalQuantization() (default is all 1s). 91 | int quant_[kDCTBlockSize]; 92 | }; 93 | 94 | class OutputImage { 95 | public: 96 | OutputImage(int w, int h); 97 | 98 | int width() const { return width_; } 99 | int height() const { return height_; } 100 | 101 | OutputImageComponent& component(int c) { return components_[c]; } 102 | const OutputImageComponent& component(int c) const { return components_[c]; } 103 | 104 | // Requires that jpg is in YUV444 format. 105 | void CopyFromJpegData(const JPEGData& jpg); 106 | 107 | void ApplyGlobalQuantization(const int q[3][kDCTBlockSize]); 108 | 109 | // If sharpen or blur are enabled, preprocesses image before downsampling U or 110 | // V to improve butteraugli score and/or reduce file size. 111 | // u_sharpen: sharpen the u channel in red areas to improve score (not as 112 | // effective as v_sharpen, blue is not so important) 113 | // u_blur: blur the u channel in some areas to reduce file size 114 | // v_sharpen: sharpen the v channel in red areas to improve score 115 | // v_blur: blur the v channel in some areas to reduce file size 116 | struct DownsampleConfig { 117 | // Default is YUV420. 118 | DownsampleConfig() : u_factor_x(2), u_factor_y(2), 119 | v_factor_x(2), v_factor_y(2), 120 | u_sharpen(true), u_blur(true), 121 | v_sharpen(true), v_blur(true), 122 | use_silver_screen(false) {} 123 | int u_factor_x; 124 | int u_factor_y; 125 | int v_factor_x; 126 | int v_factor_y; 127 | bool u_sharpen; 128 | bool u_blur; 129 | bool v_sharpen; 130 | bool v_blur; 131 | bool use_silver_screen; 132 | }; 133 | 134 | void Downsample(const DownsampleConfig& cfg); 135 | 136 | void SaveToJpegData(JPEGData* jpg) const; 137 | 138 | std::vector ToSRGB() const; 139 | 140 | std::vector ToSRGB(int xmin, int ymin, int xsize, int ysize) const; 141 | 142 | void ToLinearRGB(std::vector >* rgb) const; 143 | 144 | void ToLinearRGB(int xmin, int ymin, int xsize, int ysize, 145 | std::vector >* rgb) const; 146 | 147 | std::string FrameTypeStr() const; 148 | 149 | private: 150 | const int width_; 151 | const int height_; 152 | std::vector components_; 153 | }; 154 | 155 | } // namespace knusperli 156 | 157 | #endif // KNUSPERLI_OUTPUT_IMAGE_H_ 158 | -------------------------------------------------------------------------------- /jpeg_data.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // Data structures that represent the contents of a jpeg file. 18 | 19 | #ifndef KNUSPERLI_JPEG_DATA_H_ 20 | #define KNUSPERLI_JPEG_DATA_H_ 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | 27 | #include "jpeg_error.h" 28 | 29 | namespace knusperli { 30 | 31 | static const int kDCTBlockSize = 64; 32 | static const int kMaxComponents = 4; 33 | static const int kMaxQuantTables = 4; 34 | static const int kMaxHuffmanTables = 4; 35 | static const int kJpegHuffmanMaxBitLength = 16; 36 | static const int kJpegHuffmanAlphabetSize = 256; 37 | static const int kJpegDCAlphabetSize = 12; 38 | static const int kMaxDHTMarkers = 512; 39 | 40 | static const uint8_t kDefaultQuantMatrix[2][64] = { 41 | { 16, 11, 10, 16, 24, 40, 51, 61, 42 | 12, 12, 14, 19, 26, 58, 60, 55, 43 | 14, 13, 16, 24, 40, 57, 69, 56, 44 | 14, 17, 22, 29, 51, 87, 80, 62, 45 | 18, 22, 37, 56, 68, 109, 103, 77, 46 | 24, 35, 55, 64, 81, 104, 113, 92, 47 | 49, 64, 78, 87, 103, 121, 120, 101, 48 | 72, 92, 95, 98, 112, 100, 103, 99 }, 49 | { 17, 18, 24, 47, 99, 99, 99, 99, 50 | 18, 21, 26, 66, 99, 99, 99, 99, 51 | 24, 26, 56, 99, 99, 99, 99, 99, 52 | 47, 66, 99, 99, 99, 99, 99, 99, 53 | 99, 99, 99, 99, 99, 99, 99, 99, 54 | 99, 99, 99, 99, 99, 99, 99, 99, 55 | 99, 99, 99, 99, 99, 99, 99, 99, 56 | 99, 99, 99, 99, 99, 99, 99, 99 } 57 | }; 58 | 59 | const int kJPEGNaturalOrder[80] = { 60 | 0, 1, 8, 16, 9, 2, 3, 10, 61 | 17, 24, 32, 25, 18, 11, 4, 5, 62 | 12, 19, 26, 33, 40, 48, 41, 34, 63 | 27, 20, 13, 6, 7, 14, 21, 28, 64 | 35, 42, 49, 56, 57, 50, 43, 36, 65 | 29, 22, 15, 23, 30, 37, 44, 51, 66 | 58, 59, 52, 45, 38, 31, 39, 46, 67 | 53, 60, 61, 54, 47, 55, 62, 63, 68 | // extra entries for safety in decoder 69 | 63, 63, 63, 63, 63, 63, 63, 63, 70 | 63, 63, 63, 63, 63, 63, 63, 63 71 | }; 72 | 73 | const int kJPEGZigZagOrder[64] = { 74 | 0, 1, 5, 6, 14, 15, 27, 28, 75 | 2, 4, 7, 13, 16, 26, 29, 42, 76 | 3, 8, 12, 17, 25, 30, 41, 43, 77 | 9, 11, 18, 24, 31, 40, 44, 53, 78 | 10, 19, 23, 32, 39, 45, 52, 54, 79 | 20, 22, 33, 38, 46, 51, 55, 60, 80 | 21, 34, 37, 47, 50, 56, 59, 61, 81 | 35, 36, 48, 49, 57, 58, 62, 63 82 | }; 83 | 84 | // Quantization values for an 8x8 pixel block. 85 | struct JPEGQuantTable { 86 | JPEGQuantTable() : values(kDCTBlockSize), precision(0), 87 | index(0), is_last(true) {} 88 | 89 | std::vector values; 90 | int precision; 91 | // The index of this quantization table as it was parsed from the input JPEG. 92 | // Each DQT marker segment contains an 'index' field, and we save this index 93 | // here. Valid values are 0 to 3. 94 | int index; 95 | // Set to true if this table is the last one within its marker segment. 96 | bool is_last; 97 | }; 98 | 99 | // Huffman code and decoding lookup table used for DC and AC coefficients. 100 | struct JPEGHuffmanCode { 101 | JPEGHuffmanCode() : counts(kJpegHuffmanMaxBitLength + 1), 102 | values(kJpegHuffmanAlphabetSize + 1), 103 | slot_id(0), 104 | is_last(true) {} 105 | 106 | // Bit length histogram. 107 | std::vector counts; 108 | // Symbol values sorted by increasing bit lengths. 109 | std::vector values; 110 | // The index of the Huffman code in the current set of Huffman codes. For AC 111 | // component Huffman codes, 0x10 is added to the index. 112 | int slot_id; 113 | // Set to true if this Huffman code is the last one within its marker segment. 114 | bool is_last; 115 | }; 116 | 117 | // Huffman table indexes used for one component of one scan. 118 | struct JPEGComponentScanInfo { 119 | int comp_idx; 120 | int dc_tbl_idx; 121 | int ac_tbl_idx; 122 | }; 123 | 124 | // Contains information that is used in one scan. 125 | struct JPEGScanInfo { 126 | // Parameters used for progressive scans (named the same way as in the spec): 127 | // Ss : Start of spectral band in zig-zag sequence. 128 | // Se : End of spectral band in zig-zag sequence. 129 | // Ah : Successive approximation bit position, high. 130 | // Al : Successive approximation bit position, low. 131 | int Ss; 132 | int Se; 133 | int Ah; 134 | int Al; 135 | std::vector components; 136 | }; 137 | 138 | typedef int16_t coeff_t; 139 | 140 | // Represents one component of a jpeg file. 141 | struct JPEGComponent { 142 | JPEGComponent() : id(0), 143 | h_samp_factor(1), 144 | v_samp_factor(1), 145 | quant_idx(0), 146 | width_in_blocks(0), 147 | height_in_blocks(0) {} 148 | 149 | // One-byte id of the component. 150 | int id; 151 | // Horizontal and vertical sampling factors. 152 | // In interleaved mode, each minimal coded unit (MCU) has 153 | // h_samp_factor x v_samp_factor DCT blocks from this component. 154 | int h_samp_factor; 155 | int v_samp_factor; 156 | // The index of the quantization table used for this component. 157 | int quant_idx; 158 | // The dimensions of the component measured in 8x8 blocks. 159 | int width_in_blocks; 160 | int height_in_blocks; 161 | int num_blocks; 162 | // The DCT coefficients of this component, laid out block-by-block, divided 163 | // through the quantization matrix values. 164 | std::vector coeffs; 165 | }; 166 | 167 | // Represents a parsed jpeg file. 168 | struct JPEGData { 169 | JPEGData() : width(0), 170 | height(0), 171 | version(0), 172 | max_h_samp_factor(1), 173 | max_v_samp_factor(1), 174 | MCU_rows(0), 175 | MCU_cols(0), 176 | restart_interval(0), 177 | original_jpg(NULL), 178 | original_jpg_size(0), 179 | error(JPEG_OK) {} 180 | 181 | bool Is420() const; 182 | bool Is444() const; 183 | 184 | int width; 185 | int height; 186 | int version; 187 | int max_h_samp_factor; 188 | int max_v_samp_factor; 189 | int MCU_rows; 190 | int MCU_cols; 191 | int restart_interval; 192 | std::vector app_data; 193 | std::vector com_data; 194 | std::vector quant; 195 | std::vector huffman_code; 196 | std::vector components; 197 | std::vector scan_info; 198 | std::vector marker_order; 199 | std::vector inter_marker_data; 200 | std::string tail_data; 201 | const uint8_t* original_jpg; 202 | size_t original_jpg_size; 203 | JPEGReadError error; 204 | }; 205 | 206 | void InitJPEGDataForYUV444(int w, int h, JPEGData* jpg); 207 | void SaveQuantTables(const int q[3][kDCTBlockSize], JPEGData* jpg); 208 | 209 | } // namespace knusperli 210 | 211 | #endif // KNUSPERLI_JPEG_DATA_H_ 212 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /color_transform.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #ifndef KNUSPERLI_COLOR_TRANSFORM_H_ 18 | #define KNUSPERLI_COLOR_TRANSFORM_H_ 19 | 20 | namespace knusperli { 21 | 22 | static const int kCrToRedTable[256] = { 23 | -179, -178, -177, -175, -174, -172, -171, -170, -168, -167, -165, -164, 24 | -163, -161, -160, -158, -157, -156, -154, -153, -151, -150, -149, -147, 25 | -146, -144, -143, -142, -140, -139, -137, -136, -135, -133, -132, -130, 26 | -129, -128, -126, -125, -123, -122, -121, -119, -118, -116, -115, -114, 27 | -112, -111, -109, -108, -107, -105, -104, -102, -101, -100, -98, -97, 28 | -95, -94, -93, -91, -90, -88, -87, -86, -84, -83, -81, -80, 29 | -79, -77, -76, -74, -73, -72, -70, -69, -67, -66, -64, -63, 30 | -62, -60, -59, -57, -56, -55, -53, -52, -50, -49, -48, -46, 31 | -45, -43, -42, -41, -39, -38, -36, -35, -34, -32, -31, -29, 32 | -28, -27, -25, -24, -22, -21, -20, -18, -17, -15, -14, -13, 33 | -11, -10, -8, -7, -6, -4, -3, -1, 0, 1, 3, 4, 34 | 6, 7, 8, 10, 11, 13, 14, 15, 17, 18, 20, 21, 35 | 22, 24, 25, 27, 28, 29, 31, 32, 34, 35, 36, 38, 36 | 39, 41, 42, 43, 45, 46, 48, 49, 50, 52, 53, 55, 37 | 56, 57, 59, 60, 62, 63, 64, 66, 67, 69, 70, 72, 38 | 73, 74, 76, 77, 79, 80, 81, 83, 84, 86, 87, 88, 39 | 90, 91, 93, 94, 95, 97, 98, 100, 101, 102, 104, 105, 40 | 107, 108, 109, 111, 112, 114, 115, 116, 118, 119, 121, 122, 41 | 123, 125, 126, 128, 129, 130, 132, 133, 135, 136, 137, 139, 42 | 140, 142, 143, 144, 146, 147, 149, 150, 151, 153, 154, 156, 43 | 157, 158, 160, 161, 163, 164, 165, 167, 168, 170, 171, 172, 44 | 174, 175, 177, 178 45 | }; 46 | 47 | static const int kCbToBlueTable[256] = { 48 | -227, -225, -223, -222, -220, -218, -216, -214, -213, -211, -209, -207, 49 | -206, -204, -202, -200, -198, -197, -195, -193, -191, -190, -188, -186, 50 | -184, -183, -181, -179, -177, -175, -174, -172, -170, -168, -167, -165, 51 | -163, -161, -159, -158, -156, -154, -152, -151, -149, -147, -145, -144, 52 | -142, -140, -138, -136, -135, -133, -131, -129, -128, -126, -124, -122, 53 | -120, -119, -117, -115, -113, -112, -110, -108, -106, -105, -103, -101, 54 | -99, -97, -96, -94, -92, -90, -89, -87, -85, -83, -82, -80, 55 | -78, -76, -74, -73, -71, -69, -67, -66, -64, -62, -60, -58, 56 | -57, -55, -53, -51, -50, -48, -46, -44, -43, -41, -39, -37, 57 | -35, -34, -32, -30, -28, -27, -25, -23, -21, -19, -18, -16, 58 | -14, -12, -11, -9, -7, -5, -4, -2, 0, 2, 4, 5, 59 | 7, 9, 11, 12, 14, 16, 18, 19, 21, 23, 25, 27, 60 | 28, 30, 32, 34, 35, 37, 39, 41, 43, 44, 46, 48, 61 | 50, 51, 53, 55, 57, 58, 60, 62, 64, 66, 67, 69, 62 | 71, 73, 74, 76, 78, 80, 82, 83, 85, 87, 89, 90, 63 | 92, 94, 96, 97, 99, 101, 103, 105, 106, 108, 110, 112, 64 | 113, 115, 117, 119, 120, 122, 124, 126, 128, 129, 131, 133, 65 | 135, 136, 138, 140, 142, 144, 145, 147, 149, 151, 152, 154, 66 | 156, 158, 159, 161, 163, 165, 167, 168, 170, 172, 174, 175, 67 | 177, 179, 181, 183, 184, 186, 188, 190, 191, 193, 195, 197, 68 | 198, 200, 202, 204, 206, 207, 209, 211, 213, 214, 216, 218, 69 | 220, 222, 223, 225, 70 | }; 71 | 72 | static const int kCrToGreenTable[256] = { 73 | 5990656, 5943854, 5897052, 5850250, 5803448, 5756646, 5709844, 5663042, 74 | 5616240, 5569438, 5522636, 5475834, 5429032, 5382230, 5335428, 5288626, 75 | 5241824, 5195022, 5148220, 5101418, 5054616, 5007814, 4961012, 4914210, 76 | 4867408, 4820606, 4773804, 4727002, 4680200, 4633398, 4586596, 4539794, 77 | 4492992, 4446190, 4399388, 4352586, 4305784, 4258982, 4212180, 4165378, 78 | 4118576, 4071774, 4024972, 3978170, 3931368, 3884566, 3837764, 3790962, 79 | 3744160, 3697358, 3650556, 3603754, 3556952, 3510150, 3463348, 3416546, 80 | 3369744, 3322942, 3276140, 3229338, 3182536, 3135734, 3088932, 3042130, 81 | 2995328, 2948526, 2901724, 2854922, 2808120, 2761318, 2714516, 2667714, 82 | 2620912, 2574110, 2527308, 2480506, 2433704, 2386902, 2340100, 2293298, 83 | 2246496, 2199694, 2152892, 2106090, 2059288, 2012486, 1965684, 1918882, 84 | 1872080, 1825278, 1778476, 1731674, 1684872, 1638070, 1591268, 1544466, 85 | 1497664, 1450862, 1404060, 1357258, 1310456, 1263654, 1216852, 1170050, 86 | 1123248, 1076446, 1029644, 982842, 936040, 889238, 842436, 795634, 87 | 748832, 702030, 655228, 608426, 561624, 514822, 468020, 421218, 88 | 374416, 327614, 280812, 234010, 187208, 140406, 93604, 46802, 89 | 0, -46802, -93604, -140406, -187208, -234010, -280812, -327614, 90 | -374416, -421218, -468020, -514822, -561624, -608426, -655228, -702030, 91 | -748832, -795634, -842436, -889238, -936040, -982842, -1029644, -1076446, 92 | -1123248, -1170050, -1216852, -1263654, -1310456, -1357258, -1404060, -1450862, 93 | -1497664, -1544466, -1591268, -1638070, -1684872, -1731674, -1778476, -1825278, 94 | -1872080, -1918882, -1965684, -2012486, -2059288, -2106090, -2152892, -2199694, 95 | -2246496, -2293298, -2340100, -2386902, -2433704, -2480506, -2527308, -2574110, 96 | -2620912, -2667714, -2714516, -2761318, -2808120, -2854922, -2901724, -2948526, 97 | -2995328, -3042130, -3088932, -3135734, -3182536, -3229338, -3276140, -3322942, 98 | -3369744, -3416546, -3463348, -3510150, -3556952, -3603754, -3650556, -3697358, 99 | -3744160, -3790962, -3837764, -3884566, -3931368, -3978170, -4024972, -4071774, 100 | -4118576, -4165378, -4212180, -4258982, -4305784, -4352586, -4399388, -4446190, 101 | -4492992, -4539794, -4586596, -4633398, -4680200, -4727002, -4773804, -4820606, 102 | -4867408, -4914210, -4961012, -5007814, -5054616, -5101418, -5148220, -5195022, 103 | -5241824, -5288626, -5335428, -5382230, -5429032, -5475834, -5522636, -5569438, 104 | -5616240, -5663042, -5709844, -5756646, -5803448, -5850250, -5897052, -5943854, 105 | }; 106 | 107 | static const int kCbToGreenTable[256] = { 108 | 2919680, 2897126, 2874572, 2852018, 2829464, 2806910, 2784356, 2761802, 109 | 2739248, 2716694, 2694140, 2671586, 2649032, 2626478, 2603924, 2581370, 110 | 2558816, 2536262, 2513708, 2491154, 2468600, 2446046, 2423492, 2400938, 111 | 2378384, 2355830, 2333276, 2310722, 2288168, 2265614, 2243060, 2220506, 112 | 2197952, 2175398, 2152844, 2130290, 2107736, 2085182, 2062628, 2040074, 113 | 2017520, 1994966, 1972412, 1949858, 1927304, 1904750, 1882196, 1859642, 114 | 1837088, 1814534, 1791980, 1769426, 1746872, 1724318, 1701764, 1679210, 115 | 1656656, 1634102, 1611548, 1588994, 1566440, 1543886, 1521332, 1498778, 116 | 1476224, 1453670, 1431116, 1408562, 1386008, 1363454, 1340900, 1318346, 117 | 1295792, 1273238, 1250684, 1228130, 1205576, 1183022, 1160468, 1137914, 118 | 1115360, 1092806, 1070252, 1047698, 1025144, 1002590, 980036, 957482, 119 | 934928, 912374, 889820, 867266, 844712, 822158, 799604, 777050, 120 | 754496, 731942, 709388, 686834, 664280, 641726, 619172, 596618, 121 | 574064, 551510, 528956, 506402, 483848, 461294, 438740, 416186, 122 | 393632, 371078, 348524, 325970, 303416, 280862, 258308, 235754, 123 | 213200, 190646, 168092, 145538, 122984, 100430, 77876, 55322, 124 | 32768, 10214, -12340, -34894, -57448, -80002, -102556, -125110, 125 | -147664, -170218, -192772, -215326, -237880, -260434, -282988, -305542, 126 | -328096, -350650, -373204, -395758, -418312, -440866, -463420, -485974, 127 | -508528, -531082, -553636, -576190, -598744, -621298, -643852, -666406, 128 | -688960, -711514, -734068, -756622, -779176, -801730, -824284, -846838, 129 | -869392, -891946, -914500, -937054, -959608, -982162, -1004716, -1027270, 130 | -1049824, -1072378, -1094932, -1117486, -1140040, -1162594, -1185148, -1207702, 131 | -1230256, -1252810, -1275364, -1297918, -1320472, -1343026, -1365580, -1388134, 132 | -1410688, -1433242, -1455796, -1478350, -1500904, -1523458, -1546012, -1568566, 133 | -1591120, -1613674, -1636228, -1658782, -1681336, -1703890, -1726444, -1748998, 134 | -1771552, -1794106, -1816660, -1839214, -1861768, -1884322, -1906876, -1929430, 135 | -1951984, -1974538, -1997092, -2019646, -2042200, -2064754, -2087308, -2109862, 136 | -2132416, -2154970, -2177524, -2200078, -2222632, -2245186, -2267740, -2290294, 137 | -2312848, -2335402, -2357956, -2380510, -2403064, -2425618, -2448172, -2470726, 138 | -2493280, -2515834, -2538388, -2560942, -2583496, -2606050, -2628604, -2651158, 139 | -2673712, -2696266, -2718820, -2741374, -2763928, -2786482, -2809036, -2831590, 140 | }; 141 | 142 | static const uint8_t kRangeLimitLut[4 * 256] = { 143 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 144 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 145 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 146 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 147 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 148 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 149 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 150 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 151 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 153 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 158 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 159 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 160 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 161 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 162 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 163 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 164 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 165 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 166 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 167 | 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 168 | 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 169 | 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 170 | 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 171 | 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 172 | 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 173 | 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 174 | 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, 175 | 128, 129, 130, 131, 132, 133, 134, 135, 136, 137, 138, 139, 140, 141, 142, 143, 176 | 144, 145, 146, 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, 177 | 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 178 | 176, 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, 187, 188, 189, 190, 191, 179 | 192, 193, 194, 195, 196, 197, 198, 199, 200, 201, 202, 203, 204, 205, 206, 207, 180 | 208, 209, 210, 211, 212, 213, 214, 215, 216, 217, 218, 219, 220, 221, 222, 223, 181 | 224, 225, 226, 227, 228, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 182 | 240, 241, 242, 243, 244, 245, 246, 247, 248, 249, 250, 251, 252, 253, 254, 255, 183 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 184 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 185 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 186 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 187 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 188 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 189 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 190 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 191 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 192 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 193 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 194 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 195 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 196 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 197 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 198 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 199 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 200 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 201 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 202 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 203 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 204 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 205 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 206 | 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 207 | }; 208 | 209 | static const uint8_t* kRangeLimit = kRangeLimitLut + 384; 210 | 211 | inline void ColorTransformYCbCrToRGB(uint8_t* pixel) { 212 | int y = pixel[0]; 213 | int cb = pixel[1]; 214 | int cr = pixel[2]; 215 | pixel[0] = kRangeLimit[y + kCrToRedTable[cr]]; 216 | pixel[1] = kRangeLimit[y + 217 | ((kCrToGreenTable[cr] + kCbToGreenTable[cb]) >> 16)]; 218 | pixel[2] = kRangeLimit[y + kCbToBlueTable[cb]]; 219 | 220 | // At this point the color transform is complete, but for debugging purposes, 221 | // it can be useful to look at the Y or Cr or Cb channels, because artifacts 222 | // can be much more visible there. By setting the |cm| variable below, the 223 | // decoder will output the given channel. Cr and Cb are multiplied by 2. 224 | enum ColorMode { kY, kCb, kCr, kRgb } cm = kRgb; 225 | 226 | if (cm == kY) { 227 | pixel[0] = y; 228 | pixel[1] = y; 229 | pixel[2] = y; 230 | } else if (cm == kCb) { 231 | pixel[0] = std::max(0, std::min(255, cb * 2 - 128)); 232 | pixel[1] = std::max(0, std::min(255, cb * 2 - 128)); 233 | pixel[2] = std::max(0, std::min(255, cb * 2 - 128)); 234 | } else if (cm == kCr) { 235 | pixel[0] = std::max(0, std::min(255, cr * 2 - 128)); 236 | pixel[1] = std::max(0, std::min(255, cr * 2 - 128)); 237 | pixel[2] = std::max(0, std::min(255, cr * 2 - 128)); 238 | } 239 | } 240 | 241 | } // namespace knusperli 242 | 243 | #endif // KNUSPERLI_COLOR_TRANSFORM_H_ 244 | -------------------------------------------------------------------------------- /preprocess_downsample.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "preprocess_downsample.h" 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using std::size_t; 24 | 25 | namespace { 26 | 27 | // convolve with size*size kernel 28 | std::vector Convolve2D(const std::vector& image, int w, int h, 29 | const double* kernel, int size) { 30 | auto result = image; 31 | int size2 = size / 2; 32 | for (int i = 0; i < image.size(); i++) { 33 | int x = i % w; 34 | int y = i / w; 35 | // Avoid non-normalized results at boundary by skipping edges. 36 | if (x < size2 || x + size - size2 - 1 >= w 37 | || y < size2 || y + size - size2 - 1 >= h) { 38 | continue; 39 | } 40 | float v = 0; 41 | for (int j = 0; j < size * size; j++) { 42 | int x2 = x + j % size - size2; 43 | int y2 = y + j / size - size2; 44 | v += kernel[j] * image[y2 * w + x2]; 45 | } 46 | result[i] = v; 47 | } 48 | return result; 49 | } 50 | 51 | // convolve horizontally and vertically with 1D kernel 52 | std::vector Convolve2X(const std::vector& image, int w, int h, 53 | const double* kernel, int size, double mul) { 54 | auto temp = image; 55 | int size2 = size / 2; 56 | for (int i = 0; i < image.size(); i++) { 57 | int x = i % w; 58 | int y = i / w; 59 | // Avoid non-normalized results at boundary by skipping edges. 60 | if (x < size2 || x + size - size2 - 1 >= w) continue; 61 | float v = 0; 62 | for (int j = 0; j < size; j++) { 63 | int x2 = x + j - size2; 64 | v += kernel[j] * image[y * w + x2]; 65 | } 66 | temp[i] = v * mul; 67 | } 68 | auto result = temp; 69 | for (int i = 0; i < temp.size(); i++) { 70 | int x = i % w; 71 | int y = i / w; 72 | // Avoid non-normalized results at boundary by skipping edges. 73 | if (y < size2 || y + size - size2 - 1 >= h) continue; 74 | float v = 0; 75 | for (int j = 0; j < size; j++) { 76 | int y2 = y + j - size2; 77 | v += kernel[j] * temp[y2 * w + x]; 78 | } 79 | result[i] = v * mul; 80 | } 81 | return result; 82 | } 83 | 84 | double Normal(double x, double sigma) { 85 | static const double kInvSqrt2Pi = 0.3989422804014327; 86 | return std::exp(-x * x / (2 * sigma * sigma)) * kInvSqrt2Pi / sigma; 87 | } 88 | 89 | std::vector Sharpen(const std::vector& image, int w, int h, 90 | float sigma, float amount) { 91 | // This is only made for small sigma, e.g. 1.3. 92 | std::vector kernel(5); 93 | for (int i = 0; i < kernel.size(); i++) { 94 | kernel[i] = Normal(1.0 * i - kernel.size() / 2, sigma); 95 | } 96 | 97 | double sum = 0; 98 | for (int i = 0; i < kernel.size(); i++) sum += kernel[i]; 99 | const double mul = 1.0 / sum; 100 | 101 | std::vector result = 102 | Convolve2X(image, w, h, kernel.data(), kernel.size(), mul); 103 | for (size_t i = 0; i < image.size(); i++) { 104 | result[i] = image[i] + (image[i] - result[i]) * amount; 105 | } 106 | return result; 107 | } 108 | 109 | void Erode(int w, int h, std::vector* image) { 110 | std::vector temp = *image; 111 | for (int y = 1; y + 1 < h; y++) { 112 | for (int x = 1; x + 1 < w; x++) { 113 | size_t index = y * w + x; 114 | if (!(temp[index] && temp[index - 1] && temp[index + 1] 115 | && temp[index - w] && temp[index + w])) { 116 | (*image)[index] = 0; 117 | } 118 | } 119 | } 120 | } 121 | 122 | void Dilate(int w, int h, std::vector* image) { 123 | std::vector temp = *image; 124 | for (int y = 1; y + 1 < h; y++) { 125 | for (int x = 1; x + 1 < w; x++) { 126 | size_t index = y * w + x; 127 | if (temp[index] || temp[index - 1] || temp[index + 1] 128 | || temp[index - w] || temp[index + w]) { 129 | (*image)[index] = 1; 130 | } 131 | } 132 | } 133 | } 134 | 135 | std::vector Blur(const std::vector& image, int w, int h) { 136 | // This is only made for small sigma, e.g. 1.3. 137 | static const double kSigma = 1.3; 138 | std::vector kernel(5); 139 | for (int i = 0; i < kernel.size(); i++) { 140 | kernel[i] = Normal(1.0 * i - kernel.size() / 2, kSigma); 141 | } 142 | 143 | double sum = 0; 144 | for (int i = 0; i < kernel.size(); i++) sum += kernel[i]; 145 | const double mul = 1.0 / sum; 146 | 147 | return Convolve2X(image, w, h, kernel.data(), kernel.size(), mul); 148 | } 149 | 150 | } // namespace 151 | 152 | namespace knusperli { 153 | 154 | // Do the sharpening to the v channel, but only in areas where it will help 155 | // channel should be 2 for v sharpening, or 1 for less effective u sharpening 156 | std::vector> PreProcessChannel( 157 | int w, int h, int channel, float sigma, float amount, bool blur, 158 | bool sharpen, const std::vector>& image) { 159 | if (!blur && !sharpen) return image; 160 | 161 | // Bring in range 0.0-1.0 for Y, -0.5 - 0.5 for U and V 162 | auto yuv = image; 163 | for (int i = 0; i < yuv[0].size(); i++) { 164 | yuv[0][i] /= 255.0; 165 | yuv[1][i] = yuv[1][i] / 255.0 - 0.5; 166 | yuv[2][i] = yuv[2][i] / 255.0 - 0.5; 167 | } 168 | 169 | // Map of areas where the image is not too bright to apply the effect. 170 | std::vector darkmap(image[0].size(), false); 171 | for (int y = 0; y < h; y++) { 172 | for (int x = 0; x < w; x++) { 173 | size_t index = y * w + x; 174 | float y = yuv[0][index]; 175 | float u = yuv[1][index]; 176 | float v = yuv[2][index]; 177 | 178 | float r = y + 1.402 * v; 179 | float g = y - 0.34414 * u - 0.71414 * v; 180 | float b = y + 1.772 * u; 181 | 182 | // Parameters tuned to avoid sharpening in too bright areas, where the 183 | // effect makes it worse instead of better. 184 | if (channel == 2 && g < 0.85 && b < 0.85 && r < 0.9) { 185 | darkmap[index] = true; 186 | } 187 | if (channel == 1 && r < 0.85 && g < 0.85 && b < 0.9) { 188 | darkmap[index] = true; 189 | } 190 | } 191 | } 192 | 193 | Erode(w, h, &darkmap); 194 | Erode(w, h, &darkmap); 195 | Erode(w, h, &darkmap); 196 | 197 | // Map of areas where the image is red enough (blue in case of u channel). 198 | std::vector redmap(image[0].size(), false); 199 | for (int y = 0; y < h; y++) { 200 | for (int x = 0; x < w; x++) { 201 | size_t index = y * w + x; 202 | float u = yuv[1][index]; 203 | float v = yuv[2][index]; 204 | 205 | // Parameters tuned to allow only colors on which sharpening is useful. 206 | if (channel == 2 && 2.116 * v > -0.34414 * u + 0.2 207 | && 1.402 * v > 1.772 * u + 0.2) { 208 | redmap[index] = true; 209 | } 210 | if (channel == 1 && v < 1.263 * u - 0.1 && u > -0.33741 * v) { 211 | redmap[index] = true; 212 | } 213 | } 214 | } 215 | 216 | Dilate(w, h, &redmap); 217 | Dilate(w, h, &redmap); 218 | Dilate(w, h, &redmap); 219 | 220 | // Map of areas where to allow sharpening by combining red and dark areas 221 | std::vector sharpenmap(image[0].size(), 0); 222 | for (int y = 0; y < h; y++) { 223 | for (int x = 0; x < w; x++) { 224 | size_t index = y * w + x; 225 | sharpenmap[index] = redmap[index] && darkmap[index]; 226 | } 227 | } 228 | 229 | // Threshold for where considered an edge. 230 | const double threshold = (channel == 2 ? 0.02 : 1.0) * 127.5; 231 | 232 | static const double kEdgeMatrix[9] = { 233 | 0, -1, 0, 234 | -1, 4, -1, 235 | 0, -1, 0 236 | }; 237 | 238 | // Map of areas where to allow blurring, only where it is not too sharp 239 | std::vector blurmap(image[0].size(), false); 240 | std::vector edge = Convolve2D(yuv[channel], w, h, kEdgeMatrix, 3); 241 | for (int y = 0; y < h; y++) { 242 | for (int x = 0; x < w; x++) { 243 | size_t index = y * w + x; 244 | float u = yuv[1][index]; 245 | float v = yuv[2][index]; 246 | if (sharpenmap[index]) continue; 247 | if (!darkmap[index]) continue; 248 | if (fabs(edge[index]) < threshold && v < -0.162 * u) { 249 | blurmap[index] = true; 250 | } 251 | } 252 | } 253 | Erode(w, h, &blurmap); 254 | Erode(w, h, &blurmap); 255 | 256 | // Choose sharpened, blurred or original per pixel 257 | std::vector sharpened = Sharpen(yuv[channel], w, h, sigma, amount); 258 | std::vector blurred = Blur(yuv[channel], w, h); 259 | for (int y = 0; y < h; y++) { 260 | for (int x = 0; x < w; x++) { 261 | size_t index = y * w + x; 262 | 263 | if (sharpenmap[index] > 0) { 264 | if (sharpen) yuv[channel][index] = sharpened[index]; 265 | } else if (blurmap[index] > 0) { 266 | if (blur) yuv[channel][index] = blurred[index]; 267 | } 268 | } 269 | } 270 | 271 | // Bring back to range 0-255 272 | for (int i = 0; i < yuv[0].size(); i++) { 273 | yuv[0][i] *= 255.0; 274 | yuv[1][i] = (yuv[1][i] + 0.5) * 255.0; 275 | yuv[2][i] = (yuv[2][i] + 0.5) * 255.0; 276 | } 277 | return yuv; 278 | } 279 | 280 | namespace { 281 | 282 | inline float Clip(float val) { 283 | return std::max(0.0f, std::min(255.0f, val)); 284 | } 285 | 286 | inline float RGBToY(float r, float g, float b) { 287 | return 0.299f * r + 0.587f * g + 0.114f * b; 288 | } 289 | 290 | inline float RGBToU(float r, float g, float b) { 291 | return -0.16874f * r - 0.33126f * g + 0.5f * b + 128.0; 292 | } 293 | 294 | inline float RGBToV(float r, float g, float b) { 295 | return 0.5f * r - 0.41869f * g - 0.08131f * b + 128.0; 296 | } 297 | 298 | inline float YUVToR(float y, float u, float v) { 299 | return y + 1.402 * (v - 128.0); 300 | } 301 | 302 | inline float YUVToG(float y, float u, float v) { 303 | return y - 0.344136 * (u - 128.0) - 0.714136 * (v - 128.0); 304 | } 305 | 306 | inline float YUVToB(float y, float u, float v) { 307 | return y + 1.772 * (u - 128.0); 308 | } 309 | 310 | // TODO Use SRGB->linear conversion and a lookup-table. 311 | inline float GammaToLinear(float x) { 312 | return std::pow(x / 255.0, 2.2); 313 | } 314 | 315 | // TODO Use linear->SRGB conversion and a lookup-table. 316 | inline float LinearToGamma(float x) { 317 | return 255.0 * std::pow(x, 1.0 / 2.2); 318 | } 319 | 320 | std::vector LinearlyAveragedLuma(const std::vector& rgb) { 321 | assert(rgb.size() % 3 == 0); 322 | std::vector y(rgb.size() / 3); 323 | for (int i = 0, p = 0; p < rgb.size(); ++i, p += 3) { 324 | y[i] = LinearToGamma(RGBToY(GammaToLinear(rgb[p + 0]), 325 | GammaToLinear(rgb[p + 1]), 326 | GammaToLinear(rgb[p + 2]))); 327 | } 328 | return y; 329 | } 330 | 331 | std::vector LinearlyDownsample2x2(const std::vector& rgb_in, 332 | const int width, const int height) { 333 | assert(rgb_in.size() == 3 * width * height); 334 | int w = (width + 1) / 2; 335 | int h = (height + 1) / 2; 336 | std::vector rgb_out(3 * w * h); 337 | for (int y = 0, p = 0; y < h; ++y) { 338 | for (int x = 0; x < w; ++x) { 339 | for (int i = 0; i < 3; ++i, ++p) { 340 | rgb_out[p] = 0.0; 341 | for (int iy = 0; iy < 2; ++iy) { 342 | for (int ix = 0; ix < 2; ++ix) { 343 | int yy = std::min(height - 1, 2 * y + iy); 344 | int xx = std::min(width - 1, 2 * x + ix); 345 | rgb_out[p] += GammaToLinear(rgb_in[3 * (yy * width + xx) + i]); 346 | } 347 | } 348 | rgb_out[p] = LinearToGamma(0.25 * rgb_out[p]); 349 | } 350 | } 351 | } 352 | return rgb_out; 353 | } 354 | 355 | std::vector > RGBToYUV(const std::vector& rgb) { 356 | std::vector > yuv(3, std::vector(rgb.size() / 3)); 357 | for (int i = 0, p = 0; p < rgb.size(); ++i, p += 3) { 358 | const float r = rgb[p + 0]; 359 | const float g = rgb[p + 1]; 360 | const float b = rgb[p + 2]; 361 | yuv[0][i] = RGBToY(r, g, b); 362 | yuv[1][i] = RGBToU(r, g, b); 363 | yuv[2][i] = RGBToV(r, g, b); 364 | } 365 | return yuv; 366 | } 367 | 368 | std::vector YUVToRGB(const std::vector >& yuv) { 369 | std::vector rgb(3 * yuv[0].size()); 370 | for (int i = 0, p = 0; p < rgb.size(); ++i, p += 3) { 371 | const float y = yuv[0][i]; 372 | const float u = yuv[1][i]; 373 | const float v = yuv[2][i]; 374 | rgb[p + 0] = Clip(YUVToR(y, u, v)); 375 | rgb[p + 1] = Clip(YUVToG(y, u, v)); 376 | rgb[p + 2] = Clip(YUVToB(y, u, v)); 377 | } 378 | return rgb; 379 | } 380 | 381 | // Upsamples img_in with a box-filter, and returns an image with output 382 | // dimensions width x height. 383 | std::vector Upsample2x2(const std::vector& img_in, 384 | const int width, const int height) { 385 | int w = (width + 1) / 2; 386 | int h = (height + 1) / 2; 387 | assert(img_in.size() == w * h); 388 | std::vector img_out(width * height); 389 | for (int y = 0, p = 0; y < h; ++y) { 390 | for (int x = 0; x < w; ++x, ++p) { 391 | for (int iy = 0; iy < 2; ++iy) { 392 | for (int ix = 0; ix < 2; ++ix) { 393 | int yy = std::min(height - 1, 2 * y + iy); 394 | int xx = std::min(width - 1, 2 * x + ix); 395 | img_out[yy * width + xx] = img_in[p]; 396 | } 397 | } 398 | } 399 | } 400 | return img_out; 401 | } 402 | 403 | // Apply the "fancy upsample" filter used by libjpeg. 404 | std::vector Blur(const std::vector& img, 405 | const int width, const int height) { 406 | std::vector img_out(width * height); 407 | for (int y0 = 0; y0 < height; y0 += 2) { 408 | for (int x0 = 0; x0 < width; x0 += 2) { 409 | for (int iy = 0; iy < 2 && y0 + iy < height; ++iy) { 410 | for (int ix = 0; ix < 2 && x0 + ix < width; ++ix) { 411 | int dy = 4 * iy - 2; 412 | int dx = 4 * ix - 2; 413 | int x1 = std::min(width - 1, std::max(0, x0 + dx)); 414 | int y1 = std::min(height - 1, std::max(0, y0 + dy)); 415 | img_out[(y0 + iy) * width + x0 + ix] = 416 | (9.0 * img[y0 * width + x0] + 417 | 3.0 * img[y0 * width + x1] + 418 | 3.0 * img[y1 * width + x0] + 419 | 1.0 * img[y1 * width + x1]) / 16.0; 420 | } 421 | } 422 | } 423 | } 424 | return img_out; 425 | } 426 | 427 | std::vector YUV420ToRGB(const std::vector >& yuv420, 428 | const int width, const int height) { 429 | std::vector > yuv; 430 | yuv.push_back(yuv420[0]); 431 | std::vector u = Upsample2x2(yuv420[1], width, height); 432 | std::vector v = Upsample2x2(yuv420[2], width, height); 433 | yuv.push_back(Blur(u, width, height)); 434 | yuv.push_back(Blur(v, width, height)); 435 | return YUVToRGB(yuv); 436 | } 437 | 438 | void UpdateGuess(const std::vector& target, 439 | const std::vector& reconstructed, 440 | std::vector* guess) { 441 | assert(reconstructed.size() == guess->size()); 442 | assert(target.size() == guess->size()); 443 | for (int i = 0; i < guess->size(); ++i) { 444 | // TODO: Evaluate using a decaying constant here. 445 | (*guess)[i] = Clip((*guess)[i] - (reconstructed[i] - target[i])); 446 | } 447 | } 448 | 449 | } // namespace 450 | 451 | std::vector > RGBToYUV420( 452 | const std::vector& rgb_in, const int width, const int height) { 453 | std::vector rgbf(rgb_in.size()); 454 | for (int i = 0; i < rgb_in.size(); ++i) { 455 | rgbf[i] = static_cast(rgb_in[i]); 456 | } 457 | std::vector y_target = LinearlyAveragedLuma(rgbf); 458 | std::vector > yuv_target = 459 | RGBToYUV(LinearlyDownsample2x2(rgbf, width, height)); 460 | std::vector > yuv_guess = yuv_target; 461 | yuv_guess[0] = Upsample2x2(yuv_guess[0], width, height); 462 | // TODO: Stop early if the error is small enough. 463 | for (int iter = 0; iter < 20; ++iter) { 464 | std::vector rgb_rec = YUV420ToRGB(yuv_guess, width, height); 465 | std::vector y_rec = LinearlyAveragedLuma(rgb_rec); 466 | std::vector > yuv_rec = 467 | RGBToYUV(LinearlyDownsample2x2(rgb_rec, width, height)); 468 | UpdateGuess(y_target, y_rec, &yuv_guess[0]); 469 | UpdateGuess(yuv_target[1], yuv_rec[1], &yuv_guess[1]); 470 | UpdateGuess(yuv_target[2], yuv_rec[2], &yuv_guess[2]); 471 | } 472 | yuv_guess[1] = Upsample2x2(yuv_guess[1], width, height); 473 | yuv_guess[2] = Upsample2x2(yuv_guess[2], width, height); 474 | return yuv_guess; 475 | } 476 | 477 | } // namespace knusperli 478 | -------------------------------------------------------------------------------- /output_image.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "output_image.h" 18 | 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | #include "idct.h" 26 | #include "color_transform.h" 27 | #include "dct_double.h" 28 | #include "gamma_correct.h" 29 | #include "preprocess_downsample.h" 30 | #include "quantize.h" 31 | 32 | namespace knusperli { 33 | 34 | OutputImageComponent::OutputImageComponent(int w, int h) 35 | : width_(w), height_(h) { 36 | Reset(1, 1); 37 | } 38 | 39 | void OutputImageComponent::Reset(int factor_x, int factor_y) { 40 | factor_x_ = factor_x; 41 | factor_y_ = factor_y; 42 | width_in_blocks_ = (width_ + 8 * factor_x_ - 1) / (8 * factor_x_); 43 | height_in_blocks_ = (height_ + 8 * factor_y_ - 1) / (8 * factor_y_); 44 | num_blocks_ = width_in_blocks_ * height_in_blocks_; 45 | coeffs_ = std::vector(num_blocks_ * kDCTBlockSize); 46 | pixels_ = std::vector(width_ * height_, 128 << 4); 47 | for (int i = 0; i < kDCTBlockSize; ++i) quant_[i] = 1; 48 | } 49 | 50 | bool OutputImageComponent::IsAllZero() const { 51 | int numcoeffs = num_blocks_ * kDCTBlockSize; 52 | for (int i = 0; i < numcoeffs; ++i) { 53 | if (coeffs_[i] != 0) return false; 54 | } 55 | return true; 56 | } 57 | 58 | void OutputImageComponent::GetCoeffBlock(int block_x, int block_y, 59 | coeff_t block[kDCTBlockSize]) const { 60 | assert(block_x < width_in_blocks_); 61 | assert(block_y < height_in_blocks_); 62 | int offset = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize; 63 | memcpy(block, &coeffs_[offset], kDCTBlockSize * sizeof(coeffs_[0])); 64 | } 65 | 66 | void OutputImageComponent::ToPixels(int xmin, int ymin, int xsize, int ysize, 67 | uint8_t* out, int stride) const { 68 | assert(xmin >= 0); 69 | assert(ymin >= 0); 70 | assert(xmin < width_); 71 | assert(ymin < height_); 72 | const int yend1 = ymin + ysize; 73 | const int yend0 = std::min(yend1, height_); 74 | int y = ymin; 75 | for (; y < yend0; ++y) { 76 | const int xend1 = xmin + xsize; 77 | const int xend0 = std::min(xend1, width_); 78 | int x = xmin; 79 | int px = y * width_ + xmin; 80 | for (; x < xend0; ++x, ++px, out += stride) { 81 | *out = static_cast((pixels_[px] + 8 - (x & 1)) >> 4); 82 | } 83 | const int offset = -stride; 84 | for (; x < xend1; ++x) { 85 | *out = out[offset]; 86 | out += stride; 87 | } 88 | } 89 | for (; y < yend1; ++y) { 90 | const int offset = -stride * xsize; 91 | for (int x = 0; x < xsize; ++x) { 92 | *out = out[offset]; 93 | out += stride; 94 | } 95 | } 96 | } 97 | 98 | void OutputImageComponent::ToFloatPixels(float* out, int stride) const { 99 | assert(factor_x_ == 1); 100 | assert(factor_y_ == 1); 101 | for (int block_y = 0; block_y < height_in_blocks_; ++block_y) { 102 | for (int block_x = 0; block_x < width_in_blocks_; ++block_x) { 103 | coeff_t block[kDCTBlockSize]; 104 | GetCoeffBlock(block_x, block_y, block); 105 | double blockd[kDCTBlockSize]; 106 | for (int k = 0; k < kDCTBlockSize; ++k) { 107 | blockd[k] = block[k]; 108 | } 109 | ComputeBlockIDCTDouble(blockd); 110 | for (int iy = 0; iy < 8; ++iy) { 111 | for (int ix = 0; ix < 8; ++ix) { 112 | int y = block_y * 8 + iy; 113 | int x = block_x * 8 + ix; 114 | if (y >= height_ || x >= width_) continue; 115 | out[(y * width_ + x) * stride] = blockd[8 * iy + ix] + 128.0; 116 | } 117 | } 118 | } 119 | } 120 | } 121 | 122 | void OutputImageComponent::SetCoeffBlock(int block_x, int block_y, 123 | const coeff_t block[kDCTBlockSize]) { 124 | assert(block_x < width_in_blocks_); 125 | assert(block_y < height_in_blocks_); 126 | 127 | int offset = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize; 128 | memcpy(&coeffs_[offset], block, kDCTBlockSize * sizeof(coeffs_[0])); 129 | 130 | uint8_t idct[kDCTBlockSize]; 131 | ComputeBlockIDCT(&coeffs_[offset], idct); 132 | UpdatePixelsForBlock(block_x, block_y, idct); 133 | } 134 | 135 | void OutputImageComponent::UpdatePixelsForBlock( 136 | int block_x, int block_y, const uint8_t idct[kDCTBlockSize]) { 137 | if (factor_x_ == 1 && factor_y_ == 1) { 138 | for (int iy = 0; iy < 8; ++iy) { 139 | for (int ix = 0; ix < 8; ++ix) { 140 | int x = 8 * block_x + ix; 141 | int y = 8 * block_y + iy; 142 | if (x >= width_ || y >= height_) continue; 143 | int p = y * width_ + x; 144 | pixels_[p] = idct[8 * iy + ix] << 4; 145 | } 146 | } 147 | } else if (factor_x_ == 2 && factor_y_ == 2) { 148 | // Fill in the 10x10 pixel area in the subsampled image that will be the 149 | // basis of the upsampling. This area is enough to hold the 3x3 kernel of 150 | // the fancy upsampler around each pixel. 151 | static const int kSubsampledEdgeSize = 10; 152 | uint16_t subsampled[kSubsampledEdgeSize * kSubsampledEdgeSize]; 153 | for (int j = 0; j < kSubsampledEdgeSize; ++j) { 154 | // The order we fill in the rows is: 155 | // 8 rows intersecting the block, row below, row above 156 | const int y0 = block_y * 16 + (j < 9 ? j * 2 : -2); 157 | for (int i = 0; i < kSubsampledEdgeSize; ++i) { 158 | // The order we fill in each row is: 159 | // 8 pixels within the block, left edge, right edge 160 | const int ix = ((j < 9 ? (j + 1) * kSubsampledEdgeSize : 0) + 161 | (i < 9 ? i + 1 : 0)); 162 | const int x0 = block_x * 16 + (i < 9 ? i * 2 : -2); 163 | if (x0 < 0) { 164 | subsampled[ix] = subsampled[ix + 1]; 165 | } else if (y0 < 0) { 166 | subsampled[ix] = subsampled[ix + kSubsampledEdgeSize]; 167 | } else if (x0 >= width_) { 168 | subsampled[ix] = subsampled[ix - 1]; 169 | } else if (y0 >= height_) { 170 | subsampled[ix] = subsampled[ix - kSubsampledEdgeSize]; 171 | } else if (i < 8 && j < 8) { 172 | subsampled[ix] = idct[j * 8 + i] << 4; 173 | } else { 174 | // Reconstruct the subsampled pixels around the edge of the current 175 | // block by computing the inverse of the fancy upsampler. 176 | const int y1 = std::max(y0 - 1, 0); 177 | const int x1 = std::max(x0 - 1, 0); 178 | subsampled[ix] = (pixels_[y0 * width_ + x0] * 9 + 179 | pixels_[y1 * width_ + x1] + 180 | pixels_[y0 * width_ + x1] * -3 + 181 | pixels_[y1 * width_ + x0] * -3) >> 2; 182 | } 183 | } 184 | } 185 | 186 | // Determine area to update. 187 | int xmin = std::max(block_x * 16 - 1, 0); 188 | int xmax = std::min(block_x * 16 + 16, width_ - 1); 189 | int ymin = std::max(block_y * 16 - 1, 0); 190 | int ymax = std::min(block_y * 16 + 16, height_ - 1); 191 | 192 | // Apply the fancy upsampler on the subsampled block. 193 | for (int y = ymin; y <= ymax; ++y) { 194 | const int y0 = ((y & ~1) / 2 - block_y * 8 + 1) * kSubsampledEdgeSize; 195 | const int dy = ((y & 1) * 2 - 1) * kSubsampledEdgeSize; 196 | uint16_t* rowptr = &pixels_[y * width_]; 197 | for (int x = xmin; x <= xmax; ++x) { 198 | const int x0 = (x & ~1) / 2 - block_x * 8 + 1; 199 | const int dx = (x & 1) * 2 - 1; 200 | const int ix = x0 + y0; 201 | rowptr[x] = (subsampled[ix] * 9 + subsampled[ix + dy] * 3 + 202 | subsampled[ix + dx] * 3 + subsampled[ix + dx + dy]) >> 4; 203 | } 204 | } 205 | } else { 206 | printf("Sampling ratio not supported: factor_x = %d factor_y = %d\n", 207 | factor_x_, factor_y_); 208 | exit(1); 209 | } 210 | } 211 | 212 | void OutputImageComponent::CopyFromJpegComponent(const JPEGComponent& comp, 213 | int factor_x, int factor_y, 214 | const int* quant) { 215 | Reset(factor_x, factor_y); 216 | assert(width_in_blocks_ <= comp.width_in_blocks); 217 | assert(height_in_blocks_ <= comp.height_in_blocks); 218 | 219 | const size_t src_row_size = comp.width_in_blocks * kDCTBlockSize; 220 | const size_t coeffs_size = 221 | width_in_blocks_ * height_in_blocks_ * kDCTBlockSize; 222 | 223 | // For all coefficients in all blocks, compute their allowed range and the 224 | // naive mid value that a standard decoder would use. We will later tweak the 225 | // mid values, while keeping them within the min/max range. The modifications 226 | // to the mid offset are stored separately (in blocks_off), to ensure that 227 | // modification to one block does not yet affect evaluation of the next block; 228 | // modifications to each block are accumulated before they are applied. 229 | coeff_t* blocks_min = new coeff_t[coeffs_size]; 230 | coeff_t* blocks_max = new coeff_t[coeffs_size]; 231 | coeff_t* blocks_mid = new coeff_t[coeffs_size]; 232 | int64_t* blocks_off = new int64_t[coeffs_size]; 233 | 234 | for (int block_y = 0; block_y < height_in_blocks_; ++block_y) { 235 | const coeff_t* src_coeffs = &comp.coeffs[block_y * src_row_size]; 236 | for (int block_x = 0; block_x < width_in_blocks_; ++block_x) { 237 | for (int i = 0; i < kDCTBlockSize; ++i) { 238 | int j = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize + i; 239 | blocks_mid[j] = src_coeffs[i] * quant[i]; 240 | blocks_min[j] = src_coeffs[i] * quant[i] - (quant[i] / 2); 241 | blocks_max[j] = src_coeffs[i] * quant[i] + (quant[i] / 2); 242 | blocks_off[j] = 0; 243 | } 244 | src_coeffs += kDCTBlockSize; 245 | } 246 | } 247 | 248 | // The DCT coefficients of a gradient in 10-bit fixed-point precision. 249 | // The higher frequency coefficients are zeroed because they cause artifacts 250 | // to show up. 251 | const int kLinearGradient[8] = {318, -285, 81, -32, 252 | 17 * 0, -9 * 0, 5 * 0, -2 * 0}; 253 | 254 | // Alpha(u) * sqrt(2) in 10-bit fixed-point precision. 255 | const int kAlphaSqrt2[8] = {1024, 1448, 1448, 1448, 1448, 1448, 1448, 1448}; 256 | 257 | // Note: coefficients are 10 bits as well, so when the three are multiplied, 258 | // the number is 30-bit fixed point. 259 | 260 | // Compute adjustments to make the image continuous horizontally. 261 | for (int block_y = 0; block_y < height_in_blocks_; ++block_y) { 262 | for (int block_x = 0; block_x < width_in_blocks_ - 1; ++block_x) { 263 | int i = kDCTBlockSize * (block_y * width_in_blocks_ + block_x); 264 | int j = kDCTBlockSize * (block_y * width_in_blocks_ + block_x + 1); 265 | 266 | // Note that we only compensate discontenuities up to v = 3, not the 267 | // higher-frequency coefficients, because doing so extends artifacts, 268 | // which is worse than the discontinuity. 269 | for (int v = 0; v < 4; v++) { 270 | int64_t delta_v = 0; 271 | uint64_t hf_penalty = 0; 272 | 273 | for (int u = 0; u < 8; u++) { 274 | int alpha = kAlphaSqrt2[u]; 275 | int sign = u & 1 ? -1 : 1; 276 | int gi = blocks_mid[i + v * 8 + u]; 277 | int gj = blocks_mid[j + v * 8 + u]; 278 | delta_v += alpha * (gj - sign * gi); 279 | hf_penalty += (u * u) * (gi * gi + gj * gj); 280 | } 281 | 282 | for (int u = 0; u < 8; u++) { 283 | // If there are high-frequency patterns along this edge, then 284 | // compensate those less, because otherwise we would extend artifacts 285 | // into adjacent blocks, which is worse than the discontinuity. 286 | if (hf_penalty > 400) delta_v /= 2; 287 | int sign = u & 1 ? 1 : -1; 288 | blocks_off[i + v * 8 + u] += delta_v * kLinearGradient[u]; 289 | blocks_off[j + v * 8 + u] += delta_v * kLinearGradient[u] * sign; 290 | } 291 | } 292 | } 293 | } 294 | 295 | // Compute adjustments to make the image continuous vertically. 296 | for (int block_y = 0; block_y < height_in_blocks_ - 1; ++block_y) { 297 | for (int block_x = 0; block_x < width_in_blocks_; ++block_x) { 298 | int i = kDCTBlockSize * (block_y * width_in_blocks_ + block_x); 299 | int j = kDCTBlockSize * ((block_y + 1) * width_in_blocks_ + block_x); 300 | 301 | // Note that we only compensate discontenuities up to u = 3, not the 302 | // higher-frequency coefficients, because doing so extends artifacts, 303 | // which is worse than the discontinuity. 304 | for (int u = 0; u < 4; u++) { 305 | int64_t delta_u = 0; 306 | uint64_t hf_penalty = 0; 307 | 308 | for (int v = 0; v < 8; v++) { 309 | int alpha = kAlphaSqrt2[v]; 310 | int sign = v & 1 ? -1 : 1; 311 | int gi = blocks_mid[i + v * 8 + u]; 312 | int gj = blocks_mid[j + v * 8 + u]; 313 | delta_u += alpha * (gj - sign * gi); 314 | hf_penalty += (v * v) * (gi * gi + gj * gj); 315 | } 316 | 317 | for (int v = 0; v < 8; v++) { 318 | if (hf_penalty > 400) delta_u /= 2; 319 | int sign = v & 1 ? 1 : -1; 320 | blocks_off[i + v * 8 + u] += delta_u * kLinearGradient[v]; 321 | blocks_off[j + v * 8 + u] += delta_u * kLinearGradient[v] * sign; 322 | } 323 | } 324 | } 325 | } 326 | 327 | // Sqrt(2) / 2 with 10 bits precision behind the point. 328 | const int kHalfSqrt2 = 724; 329 | 330 | // Add offsets computed this round, and clamp new coefficients to their 331 | // legal ranges. 332 | for (int i = 0; i < coeffs_size; i++) { 333 | // |blocks_off| contains the horizontal and vertical corrections. While in 334 | // isolation they remove discontinuities perfectly, doing so introduces new 335 | // discontinuities along the other edge. By weighing both with a factor 336 | // 1/sqrt(2), the correction works best on average. The offsets were 337 | // multiplied with two 10-bit fixed-point numbers, and |kHalfSqrt2| is 10 338 | // bits as well, so shift by 30 bits and one extra bit for the factor 1/2. 339 | blocks_mid[i] += (blocks_off[i] * kHalfSqrt2) >> 31; 340 | blocks_mid[i] = std::min(blocks_mid[i], blocks_max[i]); 341 | blocks_mid[i] = std::max(blocks_mid[i], blocks_min[i]); 342 | } 343 | 344 | for (int block_y = 0; block_y < height_in_blocks_; ++block_y) { 345 | for (int block_x = 0; block_x < width_in_blocks_; ++block_x) { 346 | int j = (block_y * width_in_blocks_ + block_x) * kDCTBlockSize; 347 | SetCoeffBlock(block_x, block_y, blocks_mid + j); 348 | } 349 | } 350 | 351 | delete[] blocks_min; 352 | delete[] blocks_max; 353 | delete[] blocks_mid; 354 | delete[] blocks_off; 355 | 356 | memcpy(quant_, quant, sizeof(quant_)); 357 | } 358 | 359 | void OutputImageComponent::ApplyGlobalQuantization(const int q[kDCTBlockSize]) { 360 | for (int block_y = 0; block_y < height_in_blocks_; ++block_y) { 361 | for (int block_x = 0; block_x < width_in_blocks_; ++block_x) { 362 | coeff_t block[kDCTBlockSize]; 363 | GetCoeffBlock(block_x, block_y, block); 364 | if (QuantizeBlock(block, q)) { 365 | SetCoeffBlock(block_x, block_y, block); 366 | } 367 | } 368 | } 369 | memcpy(quant_, q, sizeof(quant_)); 370 | } 371 | 372 | OutputImage::OutputImage(int w, int h) 373 | : width_(w), 374 | height_(h), 375 | components_(3, OutputImageComponent(w, h)) {} 376 | 377 | void OutputImage::CopyFromJpegData(const JPEGData& jpg) { 378 | for (int i = 0; i < jpg.components.size(); ++i) { 379 | const JPEGComponent& comp = jpg.components[i]; 380 | assert(jpg.max_h_samp_factor % comp.h_samp_factor == 0); 381 | assert(jpg.max_v_samp_factor % comp.v_samp_factor == 0); 382 | int factor_x = jpg.max_h_samp_factor / comp.h_samp_factor; 383 | int factor_y = jpg.max_v_samp_factor / comp.v_samp_factor; 384 | assert(comp.quant_idx < jpg.quant.size()); 385 | components_[i].CopyFromJpegComponent(comp, factor_x, factor_y, 386 | &jpg.quant[comp.quant_idx].values[0]); 387 | } 388 | } 389 | 390 | namespace { 391 | 392 | void SetDownsampledCoefficients(const std::vector& pixels, 393 | int factor_x, int factor_y, 394 | OutputImageComponent* comp) { 395 | assert(pixels.size() == comp->width() * comp->height()); 396 | comp->Reset(factor_x, factor_y); 397 | for (int block_y = 0; block_y < comp->height_in_blocks(); ++block_y) { 398 | for (int block_x = 0; block_x < comp->width_in_blocks(); ++block_x) { 399 | double blockd[kDCTBlockSize]; 400 | int x0 = 8 * block_x * factor_x; 401 | int y0 = 8 * block_y * factor_y; 402 | assert(x0 < comp->width()); 403 | assert(y0 < comp->height()); 404 | for (int iy = 0; iy < 8; ++iy) { 405 | for (int ix = 0; ix < 8; ++ix) { 406 | float avg = 0.0; 407 | for (int j = 0; j < factor_y; ++j) { 408 | for (int i = 0; i < factor_x; ++i) { 409 | int x = std::min(x0 + ix * factor_x + i, comp->width() - 1); 410 | int y = std::min(y0 + iy * factor_y + j, comp->height() - 1); 411 | avg += pixels[y * comp->width() + x]; 412 | } 413 | } 414 | avg /= factor_x * factor_y; 415 | blockd[iy * 8 + ix] = avg; 416 | } 417 | } 418 | ComputeBlockDCTDouble(blockd); 419 | blockd[0] -= 1024.0; 420 | coeff_t block[kDCTBlockSize]; 421 | for (int k = 0; k < kDCTBlockSize; ++k) { 422 | block[k] = static_cast(std::round(blockd[k])); 423 | } 424 | comp->SetCoeffBlock(block_x, block_y, block); 425 | } 426 | } 427 | } 428 | 429 | } // namespace 430 | 431 | void OutputImage::Downsample(const DownsampleConfig& cfg) { 432 | if (components_[1].IsAllZero() && components_[2].IsAllZero()) { 433 | // If the image is already grayscale, nothing to do. 434 | return; 435 | } 436 | if (cfg.use_silver_screen && 437 | cfg.u_factor_x == 2 && cfg.u_factor_y == 2 && 438 | cfg.v_factor_x == 2 && cfg.v_factor_y == 2) { 439 | std::vector rgb = ToSRGB(); 440 | std::vector > yuv = RGBToYUV420(rgb, width_, height_); 441 | SetDownsampledCoefficients(yuv[0], 1, 1, &components_[0]); 442 | SetDownsampledCoefficients(yuv[1], 2, 2, &components_[1]); 443 | SetDownsampledCoefficients(yuv[2], 2, 2, &components_[2]); 444 | return; 445 | } 446 | // Get the floating-point precision YUV array represented by the set of 447 | // DCT coefficients. 448 | std::vector > yuv(3, std::vector(width_ * height_)); 449 | for (int c = 0; c < 3; ++c) { 450 | components_[c].ToFloatPixels(&yuv[c][0], 1); 451 | } 452 | 453 | yuv = PreProcessChannel(width_, height_, 2, 1.3, 0.5, 454 | cfg.u_sharpen, cfg.u_blur, yuv); 455 | yuv = PreProcessChannel(width_, height_, 1, 1.3, 0.5, 456 | cfg.v_sharpen, cfg.v_blur, yuv); 457 | 458 | // Do the actual downsampling (averaging) and forward-DCT. 459 | if (cfg.u_factor_x != 1 || cfg.u_factor_y != 1) { 460 | SetDownsampledCoefficients(yuv[1], cfg.u_factor_x, cfg.u_factor_y, 461 | &components_[1]); 462 | } 463 | if (cfg.v_factor_x != 1 || cfg.v_factor_y != 1) { 464 | SetDownsampledCoefficients(yuv[2], cfg.v_factor_x, cfg.v_factor_y, 465 | &components_[2]); 466 | } 467 | } 468 | 469 | void OutputImage::ApplyGlobalQuantization(const int q[3][kDCTBlockSize]) { 470 | for (int c = 0; c < 3; ++c) { 471 | components_[c].ApplyGlobalQuantization(&q[c][0]); 472 | } 473 | } 474 | 475 | void OutputImage::SaveToJpegData(JPEGData* jpg) const { 476 | assert(components_[0].factor_x() == 1); 477 | assert(components_[0].factor_y() == 1); 478 | jpg->width = width_; 479 | jpg->height = height_; 480 | jpg->max_h_samp_factor = 1; 481 | jpg->max_v_samp_factor = 1; 482 | jpg->MCU_cols = components_[0].width_in_blocks(); 483 | jpg->MCU_rows = components_[0].height_in_blocks(); 484 | int ncomp = components_[1].IsAllZero() && components_[2].IsAllZero() ? 1 : 3; 485 | for (int i = 1; i < ncomp; ++i) { 486 | jpg->max_h_samp_factor = std::max(jpg->max_h_samp_factor, 487 | components_[i].factor_x()); 488 | jpg->max_v_samp_factor = std::max(jpg->max_h_samp_factor, 489 | components_[i].factor_y()); 490 | jpg->MCU_cols = std::min(jpg->MCU_cols, components_[i].width_in_blocks()); 491 | jpg->MCU_rows = std::min(jpg->MCU_rows, components_[i].height_in_blocks()); 492 | } 493 | jpg->components.resize(ncomp); 494 | int q[3][kDCTBlockSize]; 495 | for (int c = 0; c < 3; ++c) { 496 | memcpy(&q[c][0], components_[c].quant(), kDCTBlockSize * sizeof(q[0][0])); 497 | } 498 | for (int c = 0; c < ncomp; ++c) { 499 | JPEGComponent* comp = &jpg->components[c]; 500 | assert(jpg->max_h_samp_factor % components_[c].factor_x() == 0); 501 | assert(jpg->max_v_samp_factor % components_[c].factor_y() == 0); 502 | comp->id = c; 503 | comp->h_samp_factor = jpg->max_h_samp_factor / components_[c].factor_x(); 504 | comp->v_samp_factor = jpg->max_v_samp_factor / components_[c].factor_y(); 505 | comp->width_in_blocks = jpg->MCU_cols * comp->h_samp_factor; 506 | comp->height_in_blocks = jpg->MCU_rows * comp->v_samp_factor; 507 | comp->num_blocks = comp->width_in_blocks * comp->height_in_blocks; 508 | comp->coeffs.resize(kDCTBlockSize * comp->num_blocks); 509 | 510 | int last_dc = 0; 511 | const coeff_t* src_coeffs = components_[c].coeffs(); 512 | coeff_t* dest_coeffs = &comp->coeffs[0]; 513 | for (int block_y = 0; block_y < comp->height_in_blocks; ++block_y) { 514 | for (int block_x = 0; block_x < comp->width_in_blocks; ++block_x) { 515 | if (block_y >= components_[c].height_in_blocks() || 516 | block_x >= components_[c].width_in_blocks()) { 517 | dest_coeffs[0] = last_dc; 518 | for (int k = 1; k < kDCTBlockSize; ++k) { 519 | dest_coeffs[k] = 0; 520 | } 521 | } else { 522 | for (int k = 0; k < kDCTBlockSize; ++k) { 523 | const int quant = q[c][k]; 524 | int coeff = src_coeffs[k]; 525 | assert(coeff % quant == 0); 526 | dest_coeffs[k] = coeff / quant; 527 | } 528 | src_coeffs += kDCTBlockSize; 529 | } 530 | last_dc = dest_coeffs[0]; 531 | dest_coeffs += kDCTBlockSize; 532 | } 533 | } 534 | } 535 | SaveQuantTables(q, jpg); 536 | } 537 | 538 | std::vector OutputImage::ToSRGB(int xmin, int ymin, 539 | int xsize, int ysize) const { 540 | std::vector rgb(xsize * ysize * 3); 541 | for (int c = 0; c < 3; ++c) { 542 | components_[c].ToPixels(xmin, ymin, xsize, ysize, &rgb[c], 3); 543 | } 544 | for (int p = 0; p < rgb.size(); p += 3) { 545 | ColorTransformYCbCrToRGB(&rgb[p]); 546 | } 547 | return rgb; 548 | } 549 | 550 | std::vector OutputImage::ToSRGB() const { 551 | return ToSRGB(0, 0, width_, height_); 552 | } 553 | 554 | void OutputImage::ToLinearRGB(int xmin, int ymin, int xsize, int ysize, 555 | std::vector >* rgb) const { 556 | const double* lut = Srgb8ToLinearTable(); 557 | std::vector rgb_pixels = ToSRGB(xmin, ymin, xsize, ysize); 558 | for (int p = 0; p < xsize * ysize; ++p) { 559 | for (int i = 0; i < 3; ++i) { 560 | (*rgb)[i][p] = lut[rgb_pixels[3 * p + i]]; 561 | } 562 | } 563 | } 564 | 565 | void OutputImage::ToLinearRGB(std::vector >* rgb) const { 566 | ToLinearRGB(0, 0, width_, height_, rgb); 567 | } 568 | 569 | std::string OutputImage::FrameTypeStr() const { 570 | char buf[128]; 571 | int len = snprintf(buf, sizeof(buf), "f%d%d%d%d%d%d", 572 | component(0).factor_x(), component(0).factor_y(), 573 | component(1).factor_x(), component(1).factor_y(), 574 | component(2).factor_x(), component(2).factor_y()); 575 | return std::string(buf, len); 576 | } 577 | 578 | } // namespace knusperli 579 | -------------------------------------------------------------------------------- /jpeg_data_reader.cc: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "jpeg_data_reader.h" 18 | 19 | #include 20 | #include 21 | 22 | #include "jpeg_huffman_decode.h" 23 | 24 | namespace knusperli { 25 | 26 | namespace { 27 | 28 | // Macros for commonly used error conditions. 29 | 30 | #define VERIFY_LEN(n) \ 31 | if (*pos + (n) > len) { \ 32 | fprintf(stderr, "Unexpected end of input: pos=%d need=%d len=%d\n", \ 33 | static_cast(*pos), static_cast(n), \ 34 | static_cast(len)); \ 35 | jpg->error = JPEG_UNEXPECTED_EOF; \ 36 | return false; \ 37 | } 38 | 39 | #define VERIFY_INPUT(var, low, high, code) \ 40 | if (var < low || var > high) { \ 41 | fprintf(stderr, "Invalid %s: %d\n", #var, static_cast(var)); \ 42 | jpg->error = JPEG_INVALID_ ## code; \ 43 | return false; \ 44 | } 45 | 46 | #define VERIFY_MARKER_END() \ 47 | if (start_pos + marker_len != *pos) { \ 48 | fprintf(stderr, "Invalid marker length: declared=%d actual=%d\n", \ 49 | static_cast(marker_len), \ 50 | static_cast(*pos - start_pos)); \ 51 | jpg->error = JPEG_WRONG_MARKER_SIZE; \ 52 | return false; \ 53 | } 54 | 55 | #define EXPECT_MARKER() \ 56 | if (pos + 2 > len || data[pos] != 0xff) { \ 57 | fprintf(stderr, "Marker byte (0xff) expected, found: %d " \ 58 | "pos=%d len=%d\n", \ 59 | (pos < len ? data[pos] : 0), static_cast(pos), \ 60 | static_cast(len)); \ 61 | jpg->error = JPEG_MARKER_BYTE_NOT_FOUND; \ 62 | return false; \ 63 | } 64 | 65 | // Returns ceil(a/b). 66 | inline int DivCeil(int a, int b) { 67 | return (a + b - 1) / b; 68 | } 69 | 70 | inline int ReadUint8(const uint8_t* data, size_t* pos) { 71 | return data[(*pos)++]; 72 | } 73 | 74 | inline int ReadUint16(const uint8_t* data, size_t* pos) { 75 | int v = (data[*pos] << 8) + data[*pos + 1]; 76 | *pos += 2; 77 | return v; 78 | } 79 | 80 | // Reads the Start of Frame (SOF) marker segment and fills in *jpg with the 81 | // parsed data. 82 | bool ProcessSOF(const uint8_t* data, const size_t len, 83 | JpegReadMode mode, size_t* pos, JPEGData* jpg) { 84 | if (jpg->width != 0) { 85 | fprintf(stderr, "Duplicate SOF marker.\n"); 86 | jpg->error = JPEG_DUPLICATE_SOF; 87 | return false; 88 | } 89 | const size_t start_pos = *pos; 90 | VERIFY_LEN(8); 91 | size_t marker_len = ReadUint16(data, pos); 92 | int precision = ReadUint8(data, pos); 93 | int height = ReadUint16(data, pos); 94 | int width = ReadUint16(data, pos); 95 | int num_components = ReadUint8(data, pos); 96 | VERIFY_INPUT(precision, 8, 8, PRECISION); 97 | VERIFY_INPUT(height, 1, 65535, HEIGHT); 98 | VERIFY_INPUT(width, 1, 65535, WIDTH); 99 | VERIFY_INPUT(num_components, 1, kMaxComponents, NUMCOMP); 100 | VERIFY_LEN(3 * num_components); 101 | jpg->height = height; 102 | jpg->width = width; 103 | jpg->components.resize(num_components); 104 | 105 | // Read sampling factors and quant table index for each component. 106 | std::vector ids_seen(256, false); 107 | for (int i = 0; i < jpg->components.size(); ++i) { 108 | const int id = ReadUint8(data, pos); 109 | if (ids_seen[id]) { // (cf. section B.2.2, syntax of Ci) 110 | fprintf(stderr, "Duplicate ID %d in SOF.\n", id); 111 | jpg->error = JPEG_DUPLICATE_COMPONENT_ID; 112 | return false; 113 | } 114 | ids_seen[id] = true; 115 | jpg->components[i].id = id; 116 | int factor = ReadUint8(data, pos); 117 | int h_samp_factor = factor >> 4; 118 | int v_samp_factor = factor & 0xf; 119 | VERIFY_INPUT(h_samp_factor, 1, 15, SAMP_FACTOR); 120 | VERIFY_INPUT(v_samp_factor, 1, 15, SAMP_FACTOR); 121 | jpg->components[i].h_samp_factor = h_samp_factor; 122 | jpg->components[i].v_samp_factor = v_samp_factor; 123 | jpg->components[i].quant_idx = ReadUint8(data, pos); 124 | jpg->max_h_samp_factor = std::max(jpg->max_h_samp_factor, h_samp_factor); 125 | jpg->max_v_samp_factor = std::max(jpg->max_v_samp_factor, v_samp_factor); 126 | } 127 | 128 | // We have checked above that none of the sampling factors are 0, so the max 129 | // sampling factors can not be 0. 130 | jpg->MCU_rows = DivCeil(jpg->height, jpg->max_v_samp_factor * 8); 131 | jpg->MCU_cols = DivCeil(jpg->width, jpg->max_h_samp_factor * 8); 132 | // Compute the block dimensions for each component. 133 | if (mode == JPEG_READ_ALL) { 134 | for (int i = 0; i < jpg->components.size(); ++i) { 135 | JPEGComponent* c = &jpg->components[i]; 136 | if (jpg->max_h_samp_factor % c->h_samp_factor != 0 || 137 | jpg->max_v_samp_factor % c->v_samp_factor != 0) { 138 | fprintf(stderr, "Non-integral subsampling ratios.\n"); 139 | jpg->error = JPEG_INVALID_SAMPLING_FACTORS; 140 | return false; 141 | } 142 | c->width_in_blocks = jpg->MCU_cols * c->h_samp_factor; 143 | c->height_in_blocks = jpg->MCU_rows * c->v_samp_factor; 144 | const uint64_t num_blocks = 145 | static_cast(c->width_in_blocks) * c->height_in_blocks; 146 | if (num_blocks > (1ull << 21)) { 147 | // Refuse to allocate more than 1 GB of memory for the coefficients, 148 | // that is 2M blocks x 64 coeffs x 2 bytes per coeff x max 4 components. 149 | // TODO Add this limit to a GuetzliParams struct. 150 | fprintf(stderr, "Image too large.\n"); 151 | jpg->error = JPEG_IMAGE_TOO_LARGE; 152 | return false; 153 | } 154 | c->num_blocks = static_cast(num_blocks); 155 | c->coeffs.resize(c->num_blocks * kDCTBlockSize); 156 | } 157 | } 158 | VERIFY_MARKER_END(); 159 | return true; 160 | } 161 | 162 | // Reads the Start of Scan (SOS) marker segment and fills in *scan_info with the 163 | // parsed data. 164 | bool ProcessSOS(const uint8_t* data, const size_t len, size_t* pos, 165 | JPEGData* jpg) { 166 | const size_t start_pos = *pos; 167 | VERIFY_LEN(3); 168 | size_t marker_len = ReadUint16(data, pos); 169 | int comps_in_scan = ReadUint8(data, pos); 170 | VERIFY_INPUT(comps_in_scan, 1, jpg->components.size(), COMPS_IN_SCAN); 171 | 172 | JPEGScanInfo scan_info; 173 | scan_info.components.resize(comps_in_scan); 174 | VERIFY_LEN(2 * comps_in_scan); 175 | std::vector ids_seen(256, false); 176 | for (int i = 0; i < comps_in_scan; ++i) { 177 | int id = ReadUint8(data, pos); 178 | if (ids_seen[id]) { // (cf. section B.2.3, regarding CSj) 179 | fprintf(stderr, "Duplicate ID %d in SOS.\n", id); 180 | jpg->error = JPEG_DUPLICATE_COMPONENT_ID; 181 | return false; 182 | } 183 | ids_seen[id] = true; 184 | bool found_index = false; 185 | for (int j = 0; j < jpg->components.size(); ++j) { 186 | if (jpg->components[j].id == id) { 187 | scan_info.components[i].comp_idx = j; 188 | found_index = true; 189 | } 190 | } 191 | if (!found_index) { 192 | fprintf(stderr, "SOS marker: Could not find component with id %d\n", id); 193 | jpg->error = JPEG_COMPONENT_NOT_FOUND; 194 | return false; 195 | } 196 | int c = ReadUint8(data, pos); 197 | int dc_tbl_idx = c >> 4; 198 | int ac_tbl_idx = c & 0xf; 199 | VERIFY_INPUT(dc_tbl_idx, 0, 3, HUFFMAN_INDEX); 200 | VERIFY_INPUT(ac_tbl_idx, 0, 3, HUFFMAN_INDEX); 201 | scan_info.components[i].dc_tbl_idx = dc_tbl_idx; 202 | scan_info.components[i].ac_tbl_idx = ac_tbl_idx; 203 | } 204 | VERIFY_LEN(3); 205 | scan_info.Ss = ReadUint8(data, pos); 206 | scan_info.Se = ReadUint8(data, pos); 207 | VERIFY_INPUT(scan_info.Ss, 0, 63, START_OF_SCAN); 208 | VERIFY_INPUT(scan_info.Se, scan_info.Ss, 63, END_OF_SCAN); 209 | int c = ReadUint8(data, pos); 210 | scan_info.Ah = c >> 4; 211 | scan_info.Al = c & 0xf; 212 | // Check that all the Huffman tables needed for this scan are defined. 213 | for (int i = 0; i < comps_in_scan; ++i) { 214 | bool found_dc_table = false; 215 | bool found_ac_table = false; 216 | for (int j = 0; j < jpg->huffman_code.size(); ++j) { 217 | int slot_id = jpg->huffman_code[j].slot_id; 218 | if (slot_id == scan_info.components[i].dc_tbl_idx) { 219 | found_dc_table = true; 220 | } else if (slot_id == scan_info.components[i].ac_tbl_idx + 16) { 221 | found_ac_table = true; 222 | } 223 | } 224 | if (scan_info.Ss == 0 && !found_dc_table) { 225 | fprintf(stderr, "SOS marker: Could not find DC Huffman table with index " 226 | "%d\n", scan_info.components[i].dc_tbl_idx); 227 | jpg->error = JPEG_HUFFMAN_TABLE_NOT_FOUND; 228 | return false; 229 | } 230 | if (scan_info.Se > 0 && !found_ac_table) { 231 | fprintf(stderr, "SOS marker: Could not find AC Huffman table with index " 232 | "%d\n", scan_info.components[i].ac_tbl_idx); 233 | jpg->error = JPEG_HUFFMAN_TABLE_NOT_FOUND; 234 | return false; 235 | } 236 | } 237 | jpg->scan_info.push_back(scan_info); 238 | VERIFY_MARKER_END(); 239 | return true; 240 | } 241 | 242 | // Reads the Define Huffman Table (DHT) marker segment and fills in *jpg with 243 | // the parsed data. Builds the Huffman decoding table in either dc_huff_lut or 244 | // ac_huff_lut, depending on the type and solt_id of Huffman code being read. 245 | bool ProcessDHT(const uint8_t* data, const size_t len, 246 | JpegReadMode mode, 247 | std::vector* dc_huff_lut, 248 | std::vector* ac_huff_lut, 249 | size_t* pos, 250 | JPEGData* jpg) { 251 | const size_t start_pos = *pos; 252 | VERIFY_LEN(2); 253 | size_t marker_len = ReadUint16(data, pos); 254 | if (marker_len == 2) { 255 | fprintf(stderr, "DHT marker: no Huffman table found\n"); 256 | jpg->error = JPEG_EMPTY_DHT; 257 | return false; 258 | } 259 | while (*pos < start_pos + marker_len) { 260 | VERIFY_LEN(1 + kJpegHuffmanMaxBitLength); 261 | JPEGHuffmanCode huff; 262 | huff.slot_id = ReadUint8(data, pos); 263 | int huffman_index = huff.slot_id; 264 | int is_ac_table = (huff.slot_id & 0x10) != 0; 265 | HuffmanTableEntry* huff_lut; 266 | if (is_ac_table) { 267 | huffman_index -= 0x10; 268 | VERIFY_INPUT(huffman_index, 0, 3, HUFFMAN_INDEX); 269 | huff_lut = &(*ac_huff_lut)[huffman_index * kJpegHuffmanLutSize]; 270 | } else { 271 | VERIFY_INPUT(huffman_index, 0, 3, HUFFMAN_INDEX); 272 | huff_lut = &(*dc_huff_lut)[huffman_index * kJpegHuffmanLutSize]; 273 | } 274 | huff.counts[0] = 0; 275 | int total_count = 0; 276 | int space = 1 << kJpegHuffmanMaxBitLength; 277 | int max_depth = 1; 278 | for (int i = 1; i <= kJpegHuffmanMaxBitLength; ++i) { 279 | int count = ReadUint8(data, pos); 280 | if (count != 0) { 281 | max_depth = i; 282 | } 283 | huff.counts[i] = count; 284 | total_count += count; 285 | space -= count * (1 << (kJpegHuffmanMaxBitLength - i)); 286 | } 287 | if (is_ac_table) { 288 | VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize, HUFFMAN_CODE); 289 | } else { 290 | VERIFY_INPUT(total_count, 0, kJpegDCAlphabetSize, HUFFMAN_CODE); 291 | } 292 | VERIFY_LEN(total_count); 293 | std::vector values_seen(256, false); 294 | for (int i = 0; i < total_count; ++i) { 295 | uint8_t value = ReadUint8(data, pos); 296 | if (!is_ac_table) { 297 | VERIFY_INPUT(value, 0, kJpegDCAlphabetSize - 1, HUFFMAN_CODE); 298 | } 299 | if (values_seen[value]) { 300 | fprintf(stderr, "Duplicate Huffman code value %d\n", value); 301 | jpg->error = JPEG_INVALID_HUFFMAN_CODE; 302 | return false; 303 | } 304 | values_seen[value] = true; 305 | huff.values[i] = value; 306 | } 307 | // Add an invalid symbol that will have the all 1 code. 308 | ++huff.counts[max_depth]; 309 | huff.values[total_count] = kJpegHuffmanAlphabetSize; 310 | space -= (1 << (kJpegHuffmanMaxBitLength - max_depth)); 311 | if (space < 0) { 312 | fprintf(stderr, "Invalid Huffman code lengths.\n"); 313 | jpg->error = JPEG_INVALID_HUFFMAN_CODE; 314 | return false; 315 | } else if (space > 0 && huff_lut[0].value != 0xffff) { 316 | // Re-initialize the values to an invalid symbol so that we can recognize 317 | // it when reading the bit stream using a Huffman code with space > 0. 318 | for (int i = 0; i < kJpegHuffmanLutSize; ++i) { 319 | huff_lut[i].bits = 0; 320 | huff_lut[i].value = 0xffff; 321 | } 322 | } 323 | huff.is_last = (*pos == start_pos + marker_len); 324 | if (mode == JPEG_READ_ALL && 325 | !BuildJpegHuffmanTable(&huff.counts[0], &huff.values[0], huff_lut)) { 326 | fprintf(stderr, "Failed to build Huffman table.\n"); 327 | jpg->error = JPEG_INVALID_HUFFMAN_CODE; 328 | return false; 329 | } 330 | jpg->huffman_code.push_back(huff); 331 | } 332 | VERIFY_MARKER_END(); 333 | return true; 334 | } 335 | 336 | // Reads the Define Quantization Table (DQT) marker segment and fills in *jpg 337 | // with the parsed data. 338 | bool ProcessDQT(const uint8_t* data, const size_t len, size_t* pos, 339 | JPEGData* jpg) { 340 | const size_t start_pos = *pos; 341 | VERIFY_LEN(2); 342 | size_t marker_len = ReadUint16(data, pos); 343 | if (marker_len == 2) { 344 | fprintf(stderr, "DQT marker: no quantization table found\n"); 345 | jpg->error = JPEG_EMPTY_DQT; 346 | return false; 347 | } 348 | while (*pos < start_pos + marker_len && jpg->quant.size() < kMaxQuantTables) { 349 | VERIFY_LEN(1); 350 | int quant_table_index = ReadUint8(data, pos); 351 | int quant_table_precision = quant_table_index >> 4; 352 | quant_table_index &= 0xf; 353 | VERIFY_INPUT(quant_table_index, 0, 3, QUANT_TBL_INDEX); 354 | VERIFY_LEN((quant_table_precision ? 2 : 1) * kDCTBlockSize); 355 | JPEGQuantTable table; 356 | table.index = quant_table_index; 357 | table.precision = quant_table_precision; 358 | for (int i = 0; i < kDCTBlockSize; ++i) { 359 | int quant_val = quant_table_precision ? 360 | ReadUint16(data, pos) : 361 | ReadUint8(data, pos); 362 | VERIFY_INPUT(quant_val, 1, 65535, QUANT_VAL); 363 | table.values[kJPEGNaturalOrder[i]] = quant_val; 364 | } 365 | table.is_last = (*pos == start_pos + marker_len); 366 | jpg->quant.push_back(table); 367 | } 368 | VERIFY_MARKER_END(); 369 | return true; 370 | } 371 | 372 | // Reads the DRI marker and saved the restart interval into *jpg. 373 | bool ProcessDRI(const uint8_t* data, const size_t len, size_t* pos, 374 | JPEGData* jpg) { 375 | if (jpg->restart_interval > 0) { 376 | fprintf(stderr, "Duplicate DRI marker.\n"); 377 | jpg->error = JPEG_DUPLICATE_DRI; 378 | return false; 379 | } 380 | const size_t start_pos = *pos; 381 | VERIFY_LEN(4); 382 | size_t marker_len = ReadUint16(data, pos); 383 | int restart_interval = ReadUint16(data, pos); 384 | jpg->restart_interval = restart_interval; 385 | VERIFY_MARKER_END(); 386 | return true; 387 | } 388 | 389 | // Saves the APP marker segment as a string to *jpg. 390 | bool ProcessAPP(const uint8_t* data, const size_t len, size_t* pos, 391 | JPEGData* jpg) { 392 | VERIFY_LEN(2); 393 | size_t marker_len = ReadUint16(data, pos); 394 | VERIFY_INPUT(marker_len, 2, 65535, MARKER_LEN); 395 | VERIFY_LEN(marker_len - 2); 396 | // Save the marker type together with the app data. 397 | std::string app_str(reinterpret_cast( 398 | &data[*pos - 3]), marker_len + 1); 399 | *pos += marker_len - 2; 400 | jpg->app_data.push_back(app_str); 401 | return true; 402 | } 403 | 404 | // Saves the COM marker segment as a string to *jpg. 405 | bool ProcessCOM(const uint8_t* data, const size_t len, size_t* pos, 406 | JPEGData* jpg) { 407 | VERIFY_LEN(2); 408 | size_t marker_len = ReadUint16(data, pos); 409 | VERIFY_INPUT(marker_len, 2, 65535, MARKER_LEN); 410 | VERIFY_LEN(marker_len - 2); 411 | std::string com_str(reinterpret_cast( 412 | &data[*pos - 2]), marker_len); 413 | *pos += marker_len - 2; 414 | jpg->com_data.push_back(com_str); 415 | return true; 416 | } 417 | 418 | // Helper structure to read bits from the entropy coded data segment. 419 | struct BitReaderState { 420 | BitReaderState(const uint8_t* data, const size_t len, size_t pos) 421 | : data_(data), len_(len) { 422 | Reset(pos); 423 | } 424 | 425 | void Reset(size_t pos) { 426 | pos_ = pos; 427 | val_ = 0; 428 | bits_left_ = 0; 429 | next_marker_pos_ = len_ - 2; 430 | FillBitWindow(); 431 | } 432 | 433 | // Returns the next byte and skips the 0xff/0x00 escape sequences. 434 | uint8_t GetNextByte() { 435 | if (pos_ >= next_marker_pos_) { 436 | ++pos_; 437 | return 0; 438 | } 439 | uint8_t c = data_[pos_++]; 440 | if (c == 0xff) { 441 | uint8_t escape = data_[pos_]; 442 | if (escape == 0) { 443 | ++pos_; 444 | } else { 445 | // 0xff was followed by a non-zero byte, which means that we found the 446 | // start of the next marker segment. 447 | next_marker_pos_ = pos_ - 1; 448 | } 449 | } 450 | return c; 451 | } 452 | 453 | void FillBitWindow() { 454 | if (bits_left_ <= 16) { 455 | while (bits_left_ <= 56) { 456 | val_ <<= 8; 457 | val_ |= (uint64_t)GetNextByte(); 458 | bits_left_ += 8; 459 | } 460 | } 461 | } 462 | 463 | int ReadBits(int nbits) { 464 | FillBitWindow(); 465 | uint64_t val = (val_ >> (bits_left_ - nbits)) & ((1ULL << nbits) - 1); 466 | bits_left_ -= nbits; 467 | return val; 468 | } 469 | 470 | // Sets *pos to the next stream position where parsing should continue. 471 | // Returns false if the stream ended too early. 472 | bool FinishStream(size_t* pos) { 473 | // Give back some bytes that we did not use. 474 | int unused_bytes_left = bits_left_ >> 3; 475 | while (unused_bytes_left-- > 0) { 476 | --pos_; 477 | // If we give back a 0 byte, we need to check if it was a 0xff/0x00 escape 478 | // sequence, and if yes, we need to give back one more byte. 479 | if (pos_ < next_marker_pos_ && 480 | data_[pos_] == 0 && data_[pos_ - 1] == 0xff) { 481 | --pos_; 482 | } 483 | } 484 | if (pos_ > next_marker_pos_) { 485 | // Data ran out before the scan was complete. 486 | fprintf(stderr, "Unexpected end of scan.\n"); 487 | return false; 488 | } 489 | *pos = pos_; 490 | return true; 491 | } 492 | 493 | const uint8_t* data_; 494 | const size_t len_; 495 | size_t pos_; 496 | uint64_t val_; 497 | int bits_left_; 498 | size_t next_marker_pos_; 499 | }; 500 | 501 | // Returns the next Huffman-coded symbol. 502 | int ReadSymbol(const HuffmanTableEntry* table, BitReaderState* br) { 503 | int nbits; 504 | br->FillBitWindow(); 505 | int val = (br->val_ >> (br->bits_left_ - 8)) & 0xff; 506 | table += val; 507 | nbits = table->bits - 8; 508 | if (nbits > 0) { 509 | br->bits_left_ -= 8; 510 | table += table->value; 511 | val = (br->val_ >> (br->bits_left_ - nbits)) & ((1 << nbits) - 1); 512 | table += val; 513 | } 514 | br->bits_left_ -= table->bits; 515 | return table->value; 516 | } 517 | 518 | // Returns the DC diff or AC value for extra bits value x and prefix code s. 519 | // See Tables F.1 and F.2 of the spec. 520 | int HuffExtend(int x, int s) { 521 | return (x < (1 << (s - 1)) ? x + ((-1) << s ) + 1 : x); 522 | } 523 | 524 | // Decodes one 8x8 block of DCT coefficients from the bit stream. 525 | bool DecodeDCTBlock(const HuffmanTableEntry* dc_huff, 526 | const HuffmanTableEntry* ac_huff, 527 | int Ss, int Se, int Al, 528 | int* eobrun, 529 | BitReaderState* br, 530 | JPEGData* jpg, 531 | coeff_t* last_dc_coeff, 532 | coeff_t* coeffs) { 533 | int s; 534 | int r; 535 | bool eobrun_allowed = Ss > 0; 536 | if (Ss == 0) { 537 | s = ReadSymbol(dc_huff, br); 538 | if (s >= kJpegDCAlphabetSize) { 539 | fprintf(stderr, "Invalid Huffman symbol %d for DC coefficient.\n", s); 540 | jpg->error = JPEG_INVALID_SYMBOL; 541 | return false; 542 | } 543 | if (s > 0) { 544 | r = br->ReadBits(s); 545 | s = HuffExtend(r, s); 546 | } 547 | s += *last_dc_coeff; 548 | const int dc_coeff = s << Al; 549 | coeffs[0] = dc_coeff; 550 | if (dc_coeff != coeffs[0]) { 551 | fprintf(stderr, "Invalid DC coefficient %d\n", dc_coeff); 552 | jpg->error = JPEG_NON_REPRESENTABLE_DC_COEFF; 553 | return false; 554 | } 555 | *last_dc_coeff = s; 556 | ++Ss; 557 | } 558 | if (Ss > Se) { 559 | return true; 560 | } 561 | if (*eobrun > 0) { 562 | --(*eobrun); 563 | return true; 564 | } 565 | for (int k = Ss; k <= Se; k++) { 566 | s = ReadSymbol(ac_huff, br); 567 | if (s >= kJpegHuffmanAlphabetSize) { 568 | fprintf(stderr, "Invalid Huffman symbol %d for AC coefficient %d\n", 569 | s, k); 570 | jpg->error = JPEG_INVALID_SYMBOL; 571 | return false; 572 | } 573 | r = s >> 4; 574 | s &= 15; 575 | if (s > 0) { 576 | k += r; 577 | if (k > Se) { 578 | fprintf(stderr, "Out-of-band coefficient %d band was %d-%d\n", 579 | k, Ss, Se); 580 | jpg->error = JPEG_OUT_OF_BAND_COEFF; 581 | return false; 582 | } 583 | if (s + Al >= kJpegDCAlphabetSize) { 584 | fprintf(stderr, "Out of range AC coefficient value: s=%d Al=%d k=%d\n", 585 | s, Al, k); 586 | jpg->error = JPEG_NON_REPRESENTABLE_AC_COEFF; 587 | return false; 588 | } 589 | r = br->ReadBits(s); 590 | s = HuffExtend(r, s); 591 | coeffs[kJPEGNaturalOrder[k]] = s << Al; 592 | } else if (r == 15) { 593 | k += 15; 594 | } else { 595 | *eobrun = 1 << r; 596 | if (r > 0) { 597 | if (!eobrun_allowed) { 598 | fprintf(stderr, "End-of-block run crossing DC coeff.\n"); 599 | jpg->error = JPEG_EOB_RUN_TOO_LONG; 600 | return false; 601 | } 602 | *eobrun += br->ReadBits(r); 603 | } 604 | break; 605 | } 606 | } 607 | --(*eobrun); 608 | return true; 609 | } 610 | 611 | bool RefineDCTBlock(const HuffmanTableEntry* ac_huff, 612 | int Ss, int Se, int Al, 613 | int* eobrun, 614 | BitReaderState* br, 615 | JPEGData* jpg, 616 | coeff_t* coeffs) { 617 | bool eobrun_allowed = Ss > 0; 618 | if (Ss == 0) { 619 | int s = br->ReadBits(1); 620 | coeff_t dc_coeff = coeffs[0]; 621 | dc_coeff |= s << Al; 622 | coeffs[0] = dc_coeff; 623 | ++Ss; 624 | } 625 | if (Ss > Se) { 626 | return true; 627 | } 628 | int p1 = 1 << Al; 629 | int m1 = (-1) << Al; 630 | int k = Ss; 631 | int r; 632 | int s; 633 | bool in_zero_run = false; 634 | if (*eobrun <= 0) { 635 | for (; k <= Se; k++) { 636 | s = ReadSymbol(ac_huff, br); 637 | if (s >= kJpegHuffmanAlphabetSize) { 638 | fprintf(stderr, "Invalid Huffman symbol %d for AC coefficient %d\n", 639 | s, k); 640 | jpg->error = JPEG_INVALID_SYMBOL; 641 | return false; 642 | } 643 | r = s >> 4; 644 | s &= 15; 645 | if (s) { 646 | if (s != 1) { 647 | fprintf(stderr, "Invalid Huffman symbol %d for AC coefficient %d\n", 648 | s, k); 649 | jpg->error = JPEG_INVALID_SYMBOL; 650 | return false; 651 | } 652 | s = br->ReadBits(1) ? p1 : m1; 653 | in_zero_run = false; 654 | } else { 655 | if (r != 15) { 656 | *eobrun = 1 << r; 657 | if (r > 0) { 658 | if (!eobrun_allowed) { 659 | fprintf(stderr, "End-of-block run crossing DC coeff.\n"); 660 | jpg->error = JPEG_EOB_RUN_TOO_LONG; 661 | return false; 662 | } 663 | *eobrun += br->ReadBits(r); 664 | } 665 | break; 666 | } 667 | in_zero_run = true; 668 | } 669 | do { 670 | coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]]; 671 | if (thiscoef != 0) { 672 | if (br->ReadBits(1)) { 673 | if ((thiscoef & p1) == 0) { 674 | if (thiscoef >= 0) { 675 | thiscoef += p1; 676 | } else { 677 | thiscoef += m1; 678 | } 679 | } 680 | } 681 | coeffs[kJPEGNaturalOrder[k]] = thiscoef; 682 | } else { 683 | if (--r < 0) { 684 | break; 685 | } 686 | } 687 | k++; 688 | } while (k <= Se); 689 | if (s) { 690 | if (k > Se) { 691 | fprintf(stderr, "Out-of-band coefficient %d band was %d-%d\n", 692 | k, Ss, Se); 693 | jpg->error = JPEG_OUT_OF_BAND_COEFF; 694 | return false; 695 | } 696 | coeffs[kJPEGNaturalOrder[k]] = s; 697 | } 698 | } 699 | } 700 | if (in_zero_run) { 701 | fprintf(stderr, "Extra zero run before end-of-block.\n"); 702 | jpg->error = JPEG_EXTRA_ZERO_RUN; 703 | return false; 704 | } 705 | if (*eobrun > 0) { 706 | for (; k <= Se; k++) { 707 | coeff_t thiscoef = coeffs[kJPEGNaturalOrder[k]]; 708 | if (thiscoef != 0) { 709 | if (br->ReadBits(1)) { 710 | if ((thiscoef & p1) == 0) { 711 | if (thiscoef >= 0) { 712 | thiscoef += p1; 713 | } else { 714 | thiscoef += m1; 715 | } 716 | } 717 | } 718 | coeffs[kJPEGNaturalOrder[k]] = thiscoef; 719 | } 720 | } 721 | } 722 | --(*eobrun); 723 | return true; 724 | } 725 | 726 | bool ProcessRestart(const uint8_t* data, const size_t len, 727 | int* next_restart_marker, BitReaderState* br, 728 | JPEGData* jpg) { 729 | size_t pos = 0; 730 | if (!br->FinishStream(&pos)) { 731 | jpg->error = JPEG_INVALID_SCAN; 732 | return false; 733 | } 734 | int expected_marker = 0xd0 + *next_restart_marker; 735 | EXPECT_MARKER(); 736 | int marker = data[pos + 1]; 737 | if (marker != expected_marker) { 738 | fprintf(stderr, "Did not find expected restart marker %d actual=%d\n", 739 | expected_marker, marker); 740 | jpg->error = JPEG_WRONG_RESTART_MARKER; 741 | return false; 742 | } 743 | br->Reset(pos + 2); 744 | *next_restart_marker += 1; 745 | *next_restart_marker &= 0x7; 746 | return true; 747 | } 748 | 749 | bool ProcessScan(const uint8_t* data, const size_t len, 750 | const std::vector& dc_huff_lut, 751 | const std::vector& ac_huff_lut, 752 | uint16_t scan_progression[kMaxComponents][kDCTBlockSize], 753 | bool is_progressive, 754 | size_t* pos, 755 | JPEGData* jpg) { 756 | if (!ProcessSOS(data, len, pos, jpg)) { 757 | return false; 758 | } 759 | JPEGScanInfo* scan_info = &jpg->scan_info.back(); 760 | bool is_interleaved = (scan_info->components.size() > 1); 761 | int MCUs_per_row; 762 | int MCU_rows; 763 | if (is_interleaved) { 764 | MCUs_per_row = jpg->MCU_cols; 765 | MCU_rows = jpg->MCU_rows; 766 | } else { 767 | const JPEGComponent& c = jpg->components[scan_info->components[0].comp_idx]; 768 | MCUs_per_row = 769 | DivCeil(jpg->width * c.h_samp_factor, 8 * jpg->max_h_samp_factor); 770 | MCU_rows = 771 | DivCeil(jpg->height * c.v_samp_factor, 8 * jpg->max_v_samp_factor); 772 | } 773 | coeff_t last_dc_coeff[kMaxComponents] = {0}; 774 | BitReaderState br(data, len, *pos); 775 | int restarts_to_go = jpg->restart_interval; 776 | int next_restart_marker = 0; 777 | int eobrun = -1; 778 | int block_scan_index = 0; 779 | const int Al = is_progressive ? scan_info->Al : 0; 780 | const int Ah = is_progressive ? scan_info->Ah : 0; 781 | const int Ss = is_progressive ? scan_info->Ss : 0; 782 | const int Se = is_progressive ? scan_info->Se : 63; 783 | const uint16_t scan_bitmask = Ah == 0 ? (0xffff << Al) : (1u << Al); 784 | const uint16_t refinement_bitmask = (1 << Al) - 1; 785 | for (int i = 0; i < scan_info->components.size(); ++i) { 786 | int comp_idx = scan_info->components[i].comp_idx; 787 | for (int k = Ss; k <= Se; ++k) { 788 | if (scan_progression[comp_idx][k] & scan_bitmask) { 789 | fprintf(stderr, "Overlapping scans: component=%d k=%d prev_mask=%d " 790 | "cur_mask=%d\n", comp_idx, k, scan_progression[i][k], 791 | scan_bitmask); 792 | jpg->error = JPEG_OVERLAPPING_SCANS; 793 | return false; 794 | } 795 | if (scan_progression[comp_idx][k] & refinement_bitmask) { 796 | fprintf(stderr, "Invalid scan order, a more refined scan was already " 797 | "done: component=%d k=%d prev_mask=%d cur_mask=%d\n", comp_idx, 798 | k, scan_progression[i][k], scan_bitmask); 799 | jpg->error = JPEG_INVALID_SCAN_ORDER; 800 | return false; 801 | } 802 | scan_progression[comp_idx][k] |= scan_bitmask; 803 | } 804 | } 805 | if (Al > 10) { 806 | fprintf(stderr, "Scan parameter Al=%d is not supported in knusperli.\n", Al); 807 | jpg->error = JPEG_NON_REPRESENTABLE_AC_COEFF; 808 | return false; 809 | } 810 | for (int mcu_y = 0; mcu_y < MCU_rows; ++mcu_y) { 811 | for (int mcu_x = 0; mcu_x < MCUs_per_row; ++mcu_x) { 812 | // Handle the restart intervals. 813 | if (jpg->restart_interval > 0) { 814 | if (restarts_to_go == 0) { 815 | if (ProcessRestart(data, len, 816 | &next_restart_marker, &br, jpg)) { 817 | restarts_to_go = jpg->restart_interval; 818 | memset(last_dc_coeff, 0, sizeof(last_dc_coeff)); 819 | if (eobrun > 0) { 820 | fprintf(stderr, "End-of-block run too long.\n"); 821 | jpg->error = JPEG_EOB_RUN_TOO_LONG; 822 | return false; 823 | } 824 | eobrun = -1; // fresh start 825 | } else { 826 | return false; 827 | } 828 | } 829 | --restarts_to_go; 830 | } 831 | // Decode one MCU. 832 | for (int i = 0; i < scan_info->components.size(); ++i) { 833 | JPEGComponentScanInfo* si = &scan_info->components[i]; 834 | JPEGComponent* c = &jpg->components[si->comp_idx]; 835 | const HuffmanTableEntry* dc_lut = 836 | &dc_huff_lut[si->dc_tbl_idx * kJpegHuffmanLutSize]; 837 | const HuffmanTableEntry* ac_lut = 838 | &ac_huff_lut[si->ac_tbl_idx * kJpegHuffmanLutSize]; 839 | int nblocks_y = is_interleaved ? c->v_samp_factor : 1; 840 | int nblocks_x = is_interleaved ? c->h_samp_factor : 1; 841 | for (int iy = 0; iy < nblocks_y; ++iy) { 842 | for (int ix = 0; ix < nblocks_x; ++ix) { 843 | int block_y = mcu_y * nblocks_y + iy; 844 | int block_x = mcu_x * nblocks_x + ix; 845 | int block_idx = block_y * c->width_in_blocks + block_x; 846 | coeff_t* coeffs = &c->coeffs[block_idx * kDCTBlockSize]; 847 | if (Ah == 0) { 848 | if (!DecodeDCTBlock(dc_lut, ac_lut, Ss, Se, Al, &eobrun, &br, jpg, 849 | &last_dc_coeff[si->comp_idx], coeffs)) { 850 | return false; 851 | } 852 | } else { 853 | if (!RefineDCTBlock(ac_lut, Ss, Se, Al, 854 | &eobrun, &br, jpg, coeffs)) { 855 | return false; 856 | } 857 | } 858 | ++block_scan_index; 859 | } 860 | } 861 | } 862 | } 863 | } 864 | if (eobrun > 0) { 865 | fprintf(stderr, "End-of-block run too long.\n"); 866 | jpg->error = JPEG_EOB_RUN_TOO_LONG; 867 | return false; 868 | } 869 | if (!br.FinishStream(pos)) { 870 | jpg->error = JPEG_INVALID_SCAN; 871 | return false; 872 | } 873 | if (*pos > len) { 874 | fprintf(stderr, "Unexpected end of file during scan. pos=%d len=%d\n", 875 | static_cast(*pos), static_cast(len)); 876 | jpg->error = JPEG_UNEXPECTED_EOF; 877 | return false; 878 | } 879 | return true; 880 | } 881 | 882 | // Changes the quant_idx field of the components to refer to the index of the 883 | // quant table in the jpg->quant array. 884 | bool FixupIndexes(JPEGData* jpg) { 885 | for (int i = 0; i < jpg->components.size(); ++i) { 886 | JPEGComponent* c = &jpg->components[i]; 887 | bool found_index = false; 888 | for (int j = 0; j < jpg->quant.size(); ++j) { 889 | if (jpg->quant[j].index == c->quant_idx) { 890 | c->quant_idx = j; 891 | found_index = true; 892 | break; 893 | } 894 | } 895 | if (!found_index) { 896 | fprintf(stderr, "Quantization table with index %d not found\n", 897 | c->quant_idx); 898 | jpg->error = JPEG_QUANT_TABLE_NOT_FOUND; 899 | return false; 900 | } 901 | } 902 | return true; 903 | } 904 | 905 | size_t FindNextMarker(const uint8_t* data, const size_t len, size_t pos) { 906 | // kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker. 907 | static const uint8_t kIsValidMarker[] = { 908 | 1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 909 | 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 0, 0, 910 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 911 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 912 | }; 913 | size_t num_skipped = 0; 914 | while (pos + 1 < len && 915 | (data[pos] != 0xff || data[pos + 1] < 0xc0 || 916 | !kIsValidMarker[data[pos + 1] - 0xc0])) { 917 | ++pos; 918 | ++num_skipped; 919 | } 920 | return num_skipped; 921 | } 922 | 923 | } // namespace 924 | 925 | bool ReadJpeg(const uint8_t* data, const size_t len, JpegReadMode mode, 926 | JPEGData* jpg) { 927 | size_t pos = 0; 928 | // Check SOI marker. 929 | EXPECT_MARKER(); 930 | int marker = data[pos + 1]; 931 | pos += 2; 932 | if (marker != 0xd8) { 933 | fprintf(stderr, "Did not find expected SOI marker, actual=%d\n", marker); 934 | jpg->error = JPEG_SOI_NOT_FOUND; 935 | return false; 936 | } 937 | int lut_size = kMaxHuffmanTables * kJpegHuffmanLutSize; 938 | std::vector dc_huff_lut(lut_size); 939 | std::vector ac_huff_lut(lut_size); 940 | bool found_sof = false; 941 | bool found_dht = false; 942 | uint16_t scan_progression[kMaxComponents][kDCTBlockSize] = { { 0 } }; 943 | 944 | bool is_progressive = false; // default 945 | do { 946 | // Read next marker. 947 | size_t num_skipped = FindNextMarker(data, len, pos); 948 | if (num_skipped > 0) { 949 | // Add a fake marker to indicate arbitrary in-between-markers data. 950 | jpg->marker_order.push_back(0xff); 951 | jpg->inter_marker_data.push_back( 952 | std::string(reinterpret_cast(&data[pos]), 953 | num_skipped)); 954 | pos += num_skipped; 955 | } 956 | EXPECT_MARKER(); 957 | marker = data[pos + 1]; 958 | pos += 2; 959 | bool ok = true; 960 | switch (marker) { 961 | case 0xc0: 962 | case 0xc1: 963 | case 0xc2: 964 | is_progressive = (marker == 0xc2); 965 | ok = ProcessSOF(data, len, mode, &pos, jpg); 966 | found_sof = true; 967 | break; 968 | case 0xc4: 969 | ok = ProcessDHT(data, len, mode, &dc_huff_lut, &ac_huff_lut, &pos, jpg); 970 | found_dht = true; 971 | break; 972 | case 0xd0: 973 | case 0xd1: 974 | case 0xd2: 975 | case 0xd3: 976 | case 0xd4: 977 | case 0xd5: 978 | case 0xd6: 979 | case 0xd7: 980 | // RST markers do not have any data. 981 | break; 982 | case 0xd9: 983 | // Found end marker. 984 | break; 985 | case 0xda: 986 | if (mode == JPEG_READ_ALL) { 987 | ok = ProcessScan(data, len, dc_huff_lut, ac_huff_lut, 988 | scan_progression, is_progressive, &pos, jpg); 989 | } 990 | break; 991 | case 0xdb: 992 | ok = ProcessDQT(data, len, &pos, jpg); 993 | break; 994 | case 0xdd: 995 | ok = ProcessDRI(data, len, &pos, jpg); 996 | break; 997 | case 0xe0: 998 | case 0xe1: 999 | case 0xe2: 1000 | case 0xe3: 1001 | case 0xe4: 1002 | case 0xe5: 1003 | case 0xe6: 1004 | case 0xe7: 1005 | case 0xe8: 1006 | case 0xe9: 1007 | case 0xea: 1008 | case 0xeb: 1009 | case 0xec: 1010 | case 0xed: 1011 | case 0xee: 1012 | case 0xef: 1013 | if (mode != JPEG_READ_TABLES) { 1014 | ok = ProcessAPP(data, len, &pos, jpg); 1015 | } 1016 | break; 1017 | case 0xfe: 1018 | if (mode != JPEG_READ_TABLES) { 1019 | ok = ProcessCOM(data, len, &pos, jpg); 1020 | } 1021 | break; 1022 | default: 1023 | fprintf(stderr, "Unsupported marker: %d pos=%d len=%d\n", 1024 | marker, static_cast(pos), static_cast(len)); 1025 | jpg->error = JPEG_UNSUPPORTED_MARKER; 1026 | ok = false; 1027 | break; 1028 | } 1029 | if (!ok) { 1030 | return false; 1031 | } 1032 | jpg->marker_order.push_back(marker); 1033 | if (mode == JPEG_READ_HEADER && found_sof && found_dht) { 1034 | break; 1035 | } 1036 | } while (marker != 0xd9); 1037 | 1038 | if (!found_sof) { 1039 | fprintf(stderr, "Missing SOF marker.\n"); 1040 | jpg->error = JPEG_SOF_NOT_FOUND; 1041 | return false; 1042 | } 1043 | 1044 | // Supplemental checks. 1045 | if (mode == JPEG_READ_ALL) { 1046 | if (pos < len) { 1047 | jpg->tail_data.assign(reinterpret_cast(&data[pos]), 1048 | len - pos); 1049 | } 1050 | if (!FixupIndexes(jpg)) { 1051 | return false; 1052 | } 1053 | if (jpg->huffman_code.size() == 0) { 1054 | // Section B.2.4.2: "If a table has never been defined for a particular 1055 | // destination, then when this destination is specified in a scan header, 1056 | // the results are unpredictable." 1057 | fprintf(stderr, "Need at least one Huffman code table.\n"); 1058 | jpg->error = JPEG_HUFFMAN_TABLE_ERROR; 1059 | return false; 1060 | } 1061 | if (jpg->huffman_code.size() >= kMaxDHTMarkers) { 1062 | fprintf(stderr, "Too many Huffman tables.\n"); 1063 | jpg->error = JPEG_HUFFMAN_TABLE_ERROR; 1064 | return false; 1065 | } 1066 | } 1067 | return true; 1068 | } 1069 | 1070 | bool ReadJpeg(const std::string& data, JpegReadMode mode, 1071 | JPEGData* jpg) { 1072 | return ReadJpeg(reinterpret_cast(data.data()), 1073 | static_cast(data.size()), 1074 | mode, jpg); 1075 | } 1076 | 1077 | } // namespace knusperli 1078 | --------------------------------------------------------------------------------