├── 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 |
--------------------------------------------------------------------------------