├── LICENSE ├── Makefile ├── README.md ├── config.h ├── encoding ├── context_predict.h ├── encoding.cpp └── encoding.h ├── export ├── write_pam.h ├── write_png.h └── write_yuv.h ├── fileio.h ├── fuif.cpp ├── fuifplay.cpp ├── image ├── image.cpp └── image.h ├── import ├── read_gif.h ├── read_jpeg.h ├── read_pam.h ├── read_png.h └── read_yuv.h ├── io.cpp ├── io.h ├── maniac ├── bit.cpp ├── bit.h ├── chance.cpp ├── chance.h ├── compound.h ├── compound_enc.h ├── rac.h ├── rac_enc.h ├── symbol.h ├── symbol_enc.h └── util.h ├── transform ├── 2dmatch.h ├── approximate.h ├── dct.h ├── palette.h ├── permute.h ├── quantize.h ├── squeeze.h ├── subsample.h ├── transform.cpp ├── transform.h ├── xyb.h ├── ycbcr.h └── ycocg.h └── util.h /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Cloudinary 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CORESOURCES=image/image.cpp transform/transform.cpp maniac/*.cpp encoding/*.cpp io.cpp 2 | SOURCES=$(CORESOURCES) fuif.cpp 3 | COREHFILES=*.h image/*.h transform/*.h maniac/*.h encoding/*.h 4 | HFILES=$(COREHFILES) import/*.h export/*.h 5 | 6 | fuif: $(SOURCES) $(HFILES) 7 | g++ -O2 -DNDEBUG -g0 -std=gnu++17 $(SOURCES) -lpng -ljpeg -o fuif 8 | 9 | fuif.prof: $(SOURCES) $(HFILES) 10 | g++ -O2 -DNDEBUG -ggdb3 -pg -std=gnu++17 $(SOURCES) -lpng -ljpeg -o fuif.prof 11 | 12 | fuif.perf: $(SOURCES) $(HFILES) 13 | g++ -O2 -DNDEBUG -ggdb3 -std=gnu++17 $(SOURCES) -lpng -ljpeg -o fuif.perf 14 | 15 | 16 | fuif.dbg: $(SOURCES) $(HFILES) 17 | g++ -DDEBUG -O0 -ggdb3 -std=gnu++17 $(SOURCES) -lpng -ljpeg -o fuif.dbg 18 | 19 | 20 | fuifplay: $(CORESOURCES) $(COREHFILES) fuifplay.cpp 21 | g++ -O2 -DNDEBUG -g0 -std=gnu++17 $(CORESOURCES) fuifplay.cpp `pkg-config --cflags --libs sdl2` -o fuifplay 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | --- 3 | **FUIF development has stopped since FUIF is subsumed in [JPEG XL](https://jpeg.org/jpegxl/), which is based on a combination of [Pik](https://github.com/google/pik) and FUIF. 4 | A royalty-free and open source reference implementation of JPEG XL is available on [GitHub](https://github.com/libjxl/libjxl). For more information, see [JPEG XL community page](https://jpegxl.info) or visit the [JPEG XL Discord server](https://discord.gg/DqkQgDRTFu).** 5 | 6 | --- 7 | 8 | # FUIF: Free Universal Image Format 9 | 10 | **WARNING: This is a research prototype. The bitstream is not finalized. Images encoded 11 | with the current version of FUIF may not (and probably will not) decode with future versions 12 | of FUIF. Use at your own risk!** 13 | 14 | FUIF is a new image format, combining ideas from JPEG, lossless WebP, and FLIF. 15 | It is a 'universal' image format in several senses: 16 | 17 | 1. it works well for any kind of image (photographic and non-photographic) 18 | 2. it can do both lossless and lossy (with the same underlying algorithm) 19 | 3. it is Responsive By Design: one file can be used, instead of needing 20 | separate images for the low-quality image placeholder, thumbnail, preview, dpr 1, dpr 2, etc 21 | 4. it is backwards compatible with JPEG, in the sense that existing JPEG images can be 22 | transcoded to FUIF losslessly (no generation loss) and effectively (smaller files) 23 | 5. it can achieve various trade-offs between encode/decode complexity and compression density 24 | 25 | 26 | In more detail: 27 | 28 | ## 1. Any kind of image 29 | 30 | FUIF supports several methods for image preprocessing and entropy coding, so it can use 31 | the methods that work best for a particular image (or a particular part of an image). 32 | 33 | For non-photographic images with few colors, a palette (color index) can be used. 34 | There is no limit on the palette size. 35 | 36 | For images with repetition (e.g. images that include text, where the same letter shapes 37 | appear in multiple locations), an optional transformation can be used to replace repetition 38 | with references (somewhat similar to JBIG2). 39 | 40 | For photographic images, the DCT transformation can be used. 41 | 42 | For raw sensor data, the format supports an arbitrary number of channels, that do not need 43 | to have the same dimensions, and that do not need to have the same value ranges either. 44 | The actual ranges can be used effectively (not just 8-bit or 16-bit like in PNG, but also 45 | things like 14-bit with a black level and white level range of 1023..15600). 46 | 47 | FUIF supports bit depths up to 28-bit per channel, unsigned or signed. 48 | Only integer values are supported (no floating point), but in principle, floating point 49 | numbers can be represented as integers plus a suitable transfer function. 50 | 51 | 52 | ## 2. Lossless and lossy 53 | 54 | FUIF uses reversible transforms (YCoCg, reversible Haar-like squeezing); 55 | optional quantization is the only source of loss. 56 | 57 | Unlike FLIF, FUIF was designed with lossy compression in mind. 58 | Unlike JPEG, FUIF can obtain fully lossless compression. 59 | Unlike WebP and JPEG 2000, FUIF uses the same algorithm for both lossy and lossless. 60 | 61 | FUIF attempts to offer state-of-the-art compression for both lossy and lossless compression. 62 | 63 | 64 | ## 3. Responsive By Design 65 | 66 | 67 | The FUIF bitstream is designed in such a way that truncating a file results in a 68 | lower resolution downscaled image. It is similar to progressive JPEG or JPEG 2000 69 | in this respect. Image formats (like WebP, HEIC/BPG, AVIF) that are derived from a video codec 70 | (VP8, HEVC, AV1) intra-frame encoding, do not support progressive decoding, since 71 | in a video codec it does not make much sense to decode a single frame progressively. 72 | 73 | FUIF was designed specifically with web delivery in mind. Instead of having to produce, 74 | store and deliver many variants of an image, scaled to various dimensions, the idea is 75 | to use a single file and truncate it depending on the dimensions it gets rendered on. 76 | This approach has several advantages: 77 | 78 | * Less storage needed (no redundancy) 79 | * Better for CDNs (cache hit is more likely) 80 | * Less bandwidth needed (e.g. if you are currently sending a LQIP, then a thumbnail, then when it gets clicked a larger image) 81 | * Smart browsers could make choices themselves, e.g. load lower resolution when on a slow or expensive connection; when the local browser cache 82 | gets too full, instead of deleting whole files, it could trim off bytes at the end, keeping a preview in cache 83 | 84 | The header of a FUIF file contains a mandatory list of truncation offsets, which makes 85 | it easy to know how many bytes should be requested or served. The following offsets are 86 | included in the header: 87 | 88 | * LQIP: the first very low-quality preview of an image (typically at 100-200 bytes) 89 | * scale 1/16 90 | * scale 1/8 91 | * scale 1/4 92 | * scale 1/2 93 | 94 | These power-of-two downscales are exact, in the sense that if you truncate the file at the given offset, 95 | the resulting image is the same as decoding the whole full-resolution image and then downscaling it. 96 | This cannot really be done in an efficient way with progressive JPEG. 97 | 98 | FUIF has a minimalistic, compact header layout, so the first bits of actual image data appear as 99 | early as possible. This makes it possible to get a LQIP within a small byte budget, while it is still 100 | the beginning of the actual full image, so you also get the actual image dimensions, truncation offsets 101 | etc. 102 | 103 | 104 | ## 4. Backwards compatible with JPEG 105 | 106 | There are a LOT of existing JPEG images out there, as well as devices that produce new JPEG images. 107 | If the only way to transcode a JPEG image to a new image format, is to decode the JPEG to pixels and 108 | encode that to the new format, then there is a problem. Either you get significant generation loss, 109 | or you get new files that are actually larger than the original. 110 | 111 | FUIF supports the 8x8 DCT transform (which is inherently lossy due to rounding errors) and the 112 | YCbCr color transform (which is also inherently lossy for the same reason). This means it can 113 | losslessly represent the actual information in a JPEG image, while still adding most of the benefits 114 | of FUIF: 115 | 116 | * LQIP and scale 1/16 (progressive JPEG starts at scale 1/8) 117 | * Minimal header overhead 118 | * Better compression 119 | 120 | Comparing FUIF to [Dropbox's Lepton](https://github.com/dropbox/lepton), which also does lossless JPEG recompression: 121 | 122 | * Lepton can obtain slightly better compression density 123 | * Lepton is faster 124 | * Lepton is bit-exact (not just image-exact) 125 | * FUIF can be decoded progressively (Lepton is anti-progressive since it encodes the AC before the DC) 126 | * FUIF can do more than just what JPEG can, for example you can add an alpha channel to an existing JPEG 127 | 128 | 129 | ## 5. Trade-offs between complexity and compression density 130 | 131 | The entropy coding of FUIF is based on MANIAC (just like FLIF). 132 | Different trade-offs between computational complexity and compression density can be obtained. 133 | Using predefined or restricted MANIAC trees (or even no trees at all), encoding can be made 134 | faster; if encode time is not an issue, then there are many ways to optimize the encoding. 135 | 136 | In principle, subsets of the formats ("profiles") could be defined 137 | (e.g. by using fixed or restricted transformations and MANIAC trees) 138 | for which both encoding and decoding can be specialized (or done in hardware), 139 | making it significantly faster. 140 | These subsets are not currently defined nor implemented. 141 | 142 | FUIF is also well-suited for adaptive compression (i.e. having different qualities in 143 | different regions of the image). The MANIAC tree can represent an arbitrary segmentation 144 | of the image; there is no notion of (having to align with) macroblocks. This makes it easy 145 | to effectively use different context models for different regions of the image. 146 | 147 | 148 | ## FUIF Design Principles 149 | 150 | The following goals or principles have guided the design of FUIF: 151 | 152 | * FUIF is an __image format, not a container__. FUIF stores pixels and the metadata needed to 153 | render the image, but that's it. Anything else, like Exif metadata, comments, 154 | rotation, crop coordinates, layers, tiling, image sequences, and so on is a task 155 | for the container format (e.g. HEIF), not for the image format. 156 | FUIF does support simple 'filmstrip' animation, so it can be used to recompress GIF and APNG, 157 | but it is not a video codec. 158 | _Rationale: this is a matter of separation of concerns, as well as avoiding duplication of functionality. 159 | For simple animations, FUIF might be suitable, but in general we think that most animations 160 | should be encoded as a short (looping) video 161 | (taking advantage of inter-frame prediction, which is out of the scope of an image format)._ 162 | 163 | * FUIF is optimized for __delivery__, not storage. It is progressive / "responsive by design". One single file 164 | can be used instead of having to downscale a high-resolution original to various target resolutions. 165 | The bitstream does not require seeking and a meaningful placeholder/preview can be shown from the first few 166 | hundred bytes. Perhaps it is possible to achieve better compression density and/or faster encoding by not 167 | optimizing for delivery (e.g. by predicting DC from AC like Lepton does). 168 | If the use case is (essentially) write-only archival then maybe it is worth it to sacrifice progressive decoding, 169 | and FUIF can in principle be used in such a way, but that's not the primary target. 170 | _Rationale: the variety of display devices (from smart watches to huge 8K screens) and network conditions 171 | (from 2G on the road in rural areas to very high speed at home) is a huge challenge. 172 | Making many files for the same image is not the best approach, e.g. in terms of CDN cache hits. 173 | Moreover browsers get few opportunities to be smart and adjust the image resolution and quality 174 | automatically based on the device and network conditions. Non-progressive image formats result 175 | in a bad user experience when bandwidth is an issue._ 176 | 177 | * __'Honest' compression artifacts__. 178 | There are various approaches to how to do lossy compression. Different techniques lead to different artifacts. 179 | Some artifacts are less "annoying" than other artifacts. For example, blurring, smearing and mild ringing are probably 180 | not very annoying (or even desireable to some, because it might eliminate noise and increase perceived sharpness), 181 | while pixelation, blockiness and color banding are annoying and obvious compression artifacts. 182 | Also, some artifacts are not very "honest", in the sense that the image looks deceptively better than 183 | it actually is. For example, JBIG2 in lossy mode or HEIC at low bitrates can produce images that look like they 184 | are high-quality (e.g. they have sharp details at the pixel level), 185 | but they are actually very different from the uncompressed image. 186 | For example, JPEG artifacts are "honest" and "annoying", while WebP and HEIC artifacts are "not honest" and "not annoying". 187 | FUIF aims for compression artifacts that are "honest" and "not annoying". At low bitrates, pixelation will become 188 | obvious at a 1:1 scale, but the overall image fidelity will still be as high as possible 189 | (e.g. comparing a downscaled lossy FUIF image to a downscaled original). 190 | _Rationale: this is a matter of preference, but we think that image fidelity is more important than hiding the fact 191 | that lossy compression was used. An image format should not act as an artistic filter that modifies an image more than 192 | necessary. At least that's our opinion._ 193 | 194 | * FUIF is royalty __free__ and it has a reference implementation that is free and open source software. 195 | _Rationale: we don't want no patent mess, please._ 196 | 197 | * FUIF is __legacy-friendly__ but not backwards compatible. 198 | Unlike JPEG XT, the FUIF bitstream is completely different from legacy JPEG, so it is not backwards compatible 199 | (cannot be read by existing JPEG decoders). But unlike most other recent image formats (e.g. WebP, HEIC, AVIF), 200 | existing JPEG images can be converted losslessly and effectively to FUIF, 201 | potentially with added information like alpha or depth channels. 202 | _Rationale: backwards compatibility is obviously convenient, but it has serious downsides. 203 | It implies that the compression density cannot be improved compared to legacy JPEG. 204 | It also means that none of the new features (like e.g. alpha) are guaranteed to be rendered correctly, 205 | since (at least initially) most decoders will ignore any extensions of the legacy JPEG bitstream. 206 | So for these reasons, not being backwards compatible is a better choice. 207 | However it is nice to be able to transcode existing JPEG images to FUIF without generation loss and while saving bytes._ 208 | 209 | 210 | ## TL;DR feature summary 211 | 212 | * Lossless and lossy, works well for photographic and non-photographic 213 | * Arbitrary number of channels (grayscale, RGB, CMYK, RGBA, CMYKA, CMYKA+depth, etc) 214 | * Bit depth up to 28-bit, arbitrary actual ranges of pixel values 215 | * Progressive/Responsive by design: single-file responsive images 216 | * Minimal header overhead, so LQIP in say a 200-byte budget is possible 217 | * Backwards compatible with JPEG (can losslessly and efficiently represent legacy JPEG files) 218 | * Adjustable trade-off between encode/decode complexity and compression density 219 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define HAS_ENCODER 4 | 5 | #define MAX_BIT_DEPTH 15 6 | 7 | // MAX_BIT_DEPTH is the maximum bit depth of the absolute values of the numbers that actually get encoded 8 | // Squeeze residuals plus YCoCg can result in 17-bit absolute values on 16-bit input, so 17 is needed to encode 16-bit input with default options 9 | // Higher bit depth is needed when DCT is used on 16-bit input. 10 | 11 | //#define MAX_BIT_DEPTH 17 12 | 13 | // The above compile-time constant only determines the size of the chance tables in the MANIAC trees, 14 | // and in any case the maximum bit depth is limited by the integer type used in the channel buffers 15 | // (currently int32_t, which means at most 30-bit unsigned input) 16 | //#define MAX_BIT_DEPTH 30 17 | 18 | 19 | // 2 byte improvement needed before splitting a MANIAC leaf node 20 | #define CONTEXT_TREE_SPLIT_THRESHOLD (5461*8*2) 21 | 22 | #define CONTEXT_TREE_COUNT_DIV 1 23 | #define CONTEXT_TREE_COUNT_POWER 0.166666667 24 | 25 | //#define CONTEXT_TREE_COUNT_DIV 30 26 | //#define CONTEXT_TREE_COUNT_POWER 1 27 | #define CONTEXT_TREE_MIN_SUBTREE_SIZE 10 28 | #define CONTEXT_TREE_MIN_COUNT_ENCODER 1 29 | //#define CONTEXT_TREE_MIN_COUNT_ENCODER -1 30 | 31 | 32 | 33 | 34 | /**************************************************/ 35 | /* DANGER ZONE: OPTIONS THAT CHANGE THE BITSTREAM */ 36 | /* If you modify these, the bitstream format */ 37 | /* changes, so it is no longer compatible! */ 38 | /**************************************************/ 39 | 40 | 41 | // Default squeeze will ensure that the first 'scan' fits in a 8x8 rectangle 42 | #define MAX_FIRST_PREVIEW_SIZE 8 43 | // Round truncation offsets to a multiples of 1 byte (using less precise offsets requires a more careful implementation of partial decode) 44 | #define TRUNCATION_OFFSET_RESOLUTION 1 45 | 46 | 47 | #ifdef _MSC_VER 48 | #define ATTRIBUTE_HOT 49 | #else 50 | #define ATTRIBUTE_HOT __attribute__ ((hot)) 51 | #endif 52 | 53 | #include "maniac/rac.h" 54 | 55 | #include "fileio.h" 56 | #include "io.h" 57 | #include "util.h" 58 | 59 | template using RacIn = RacInput24; 60 | 61 | #ifdef HAS_ENCODER 62 | template using RacOut = RacOutput24; 63 | #endif 64 | 65 | //#define CONTEXT_TREE_MIN_COUNT -1 66 | //#define CONTEXT_TREE_MAX_COUNT -1 67 | 68 | 69 | // bounds for node counters 70 | #define CONTEXT_TREE_MIN_COUNT 1 71 | #define CONTEXT_TREE_MAX_COUNT 255 72 | //#define CONTEXT_TREE_MAX_COUNT 512 73 | 74 | #include "maniac/compound.h" 75 | 76 | typedef SimpleBitChance FUIFBitChanceMeta; 77 | typedef SimpleBitChance FUIFBitChancePass1; 78 | typedef SimpleBitChance FUIFBitChancePass2; 79 | typedef SimpleBitChance FUIFBitChanceTree; 80 | 81 | -------------------------------------------------------------------------------- /encoding/context_predict.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | FUIF - FREE UNIVERSAL IMAGE FORMAT 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #include "../maniac/util.h" 27 | 28 | 29 | // should only be called with positive values 30 | // kind of like the ilog2, just a bit more resolution at the low values 31 | // ilog2 0,1 = 0 32 | // ilog2 2,3 = 1 33 | // ilog2 4,5,6,7 = 2 34 | // ilog2 8-15 = 3 35 | // ilog2 16-31 = 4 36 | // ilog2 32-63 = 5 37 | 38 | inline pixel_type foolog(pixel_type x) ATTRIBUTE_HOT; 39 | inline pixel_type foolog(pixel_type x) { 40 | return maniac::util::ilog2(x<<1); 41 | /* 42 | if (x > 31) return maniac::util::ilog2(x) + 4; // ilog2(32) == 5 43 | static const pixel_type foolog_table[32] = 44 | { 0, 1, 2, 3, 3, 4, 4, 4, 45 | 5, 5, 5, 5, 6, 6, 6, 6, 46 | 6, 7, 7, 7, 7, 7, 7, 7, 47 | 8, 8, 8, 8, 8, 8, 8, 8 }; 48 | return foolog_table[x]; 49 | */ 50 | } 51 | 52 | // signed log-like thing 53 | inline pixel_type slog(pixel_type x) ATTRIBUTE_HOT; 54 | inline pixel_type slog(pixel_type x) { 55 | // pixel_type a = foolog(abs(x)); 56 | // if (x >= 0) return a; else return -a; 57 | // printf("value %i, foolog %i\n",x,foolog(abs(x))); 58 | if (x == 0) return 0; 59 | else if (x > 0) return (sizeof(unsigned int) * 8 - __builtin_clz(x)); 60 | else return -(sizeof(unsigned int) * 8 - __builtin_clz(-x)); 61 | } 62 | // absolute value (wrapped into a function to make it easier to experiment with other functions) 63 | pixel_type fooabs(pixel_type x) { 64 | return abs(x); 65 | } 66 | 67 | void init_properties(Ranges &pr, const Image &image, int beginc, int endc, fuif_options &options) { 68 | int offset=0; 69 | // for (int j=beginc-1; j>=image.nb_meta_channels && offset < options.max_properties; j--) { 70 | for (int j=beginc-1; j>=0 && offset < options.max_properties; j--) { 71 | if (image.channel[j].minval == image.channel[j].maxval) continue; 72 | if (image.channel[j].hshift < 0) continue; 73 | int minval = image.channel[j].minval; 74 | if (minval > 0) minval = 0; 75 | int maxval = image.channel[j].maxval; 76 | if (maxval < 0) maxval = 0; 77 | pr.push_back(std::pair(0,fooabs((maxval > -minval ? maxval : minval)))); 78 | offset++; 79 | pr.push_back(std::pair(slog(minval),slog(maxval))); 80 | offset++; 81 | // pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 82 | // offset++; 83 | } 84 | 85 | pixel_type minval = LARGEST_VAL; 86 | pixel_type maxval = SMALLEST_VAL; 87 | int maxh = 0; 88 | int maxw = 0; 89 | for (int j=beginc; j<=endc; j++) { 90 | if (image.channel[j].minval < minval) minval = image.channel[j].minval; 91 | if (image.channel[j].maxval > maxval) maxval = image.channel[j].maxval; 92 | if (image.channel[j].h > maxh) maxh = image.channel[j].h; 93 | if (image.channel[j].w > maxw) maxw = image.channel[j].w; 94 | } 95 | if (minval > 0) minval = 0; 96 | if (maxval < 0) maxval = 0; 97 | 98 | // printf("range = %i..%i\n",minval,maxval); 99 | // neighbors 100 | pr.push_back(std::pair(0,MAX(fooabs(minval),fooabs(maxval)))); 101 | pr.push_back(std::pair(0,MAX(fooabs(minval),fooabs(maxval)))); 102 | pr.push_back(std::pair(slog(minval),slog(maxval))); 103 | pr.push_back(std::pair(slog(minval),slog(maxval))); 104 | 105 | // location 106 | pr.push_back(std::pair(0,maxh-1)); 107 | pr.push_back(std::pair(0,maxw-1)); 108 | 109 | // blah 110 | pr.push_back(std::pair(minval+minval-maxval,maxval+maxval-minval)); 111 | pr.push_back(std::pair(minval+minval-maxval,maxval+maxval-minval)); 112 | 113 | // FFV1 114 | pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 115 | pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 116 | pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 117 | pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 118 | pr.push_back(std::pair(slog(minval-maxval),slog(maxval-minval))); 119 | 120 | } 121 | 122 | 123 | 124 | pixel_type predict_and_compute_properties(Properties &p, const Channel &ch, int x, int y, int predictor, int offset=0) ATTRIBUTE_HOT; 125 | pixel_type predict_and_compute_properties(Properties &p, const Channel &ch, int x, int y, int predictor, int offset) { 126 | pixel_type left = (x ? ch.value_nocheck(y,x-1) : ch.zero); 127 | pixel_type top = (y ? ch.value_nocheck(y-1,x) : ch.zero); 128 | pixel_type topleft = (x && y ? ch.value_nocheck(y-1,x-1) : left); 129 | pixel_type topright = (x+11 ? ch.value_nocheck(y,x-2) : left); 133 | pixel_type toptop = (y>1 ? ch.value_nocheck(y-2,x) : top); 134 | 135 | // neighbors 136 | p[offset++] = fooabs(top); 137 | p[offset++] = fooabs(left); 138 | p[offset++] = slog(top); 139 | p[offset++] = slog(left); 140 | 141 | // location 142 | p[offset++] = y; 143 | p[offset++] = x; 144 | 145 | // local gradients 146 | p[offset++] = left+top-topleft; 147 | p[offset++] = topleft+topright-top; 148 | 149 | // FFV1 context properties 150 | p[offset++] = slog(left-topleft); 151 | p[offset++] = slog(topleft-top); 152 | p[offset++] = slog(top-topright); 153 | p[offset++] = slog(top-toptop); 154 | p[offset++] = slog(left-leftleft); 155 | // } 156 | 157 | switch (predictor) { 158 | case 0: return ch.zero; 159 | case 1: return (left+top)/2; 160 | case 2: return median3((pixel_type) (left+top-topleft), left, top); 161 | case 3: return left; 162 | case 4: return top; 163 | case 5: return (left+topleft+top+topright)/4; 164 | case 6: return CLAMP(left+top-topleft, ch.minval, ch.maxval); 165 | default: return median3((pixel_type) (left+top-topleft), left, top); 166 | } 167 | 168 | } 169 | 170 | pixel_type predict_and_compute_properties_no_edge_case(Properties &p, const Channel &ch, int x, int y, int offset=0) ATTRIBUTE_HOT; 171 | pixel_type predict_and_compute_properties_no_edge_case(Properties &p, const Channel &ch, int x, int y, int offset) { 172 | assert(x>1); 173 | assert(y>1); 174 | assert(x+1=image.nb_meta_channels && offset < options.max_properties; j--) { 219 | if (image.channel[j].minval == image.channel[j].maxval) continue; 220 | int rx = ox >> image.channel[j].hshift; 221 | int ry = oy >> image.channel[j].vshift; 222 | if (rx >= image.channel[j].w) rx = image.channel[j].w-1; 223 | if (ry >= image.channel[j].h) ry = image.channel[j].h-1; 224 | pixel_type v = image.channel[j].value_nocheck(ry,rx); 225 | p[offset++] = fooabs(v); 226 | p[offset++] = slog(v); 227 | // p[offset++] = slog(image.channel[j].value(ry,rx) - image.channel[j].value(ry,(rx?rx-1:1))); 228 | } 229 | return predict_and_compute_properties(p,ch,x,y,predictor,offset); 230 | } 231 | */ 232 | 233 | void precompute_references(const Channel &ch, int y, const Image &image, int i, fuif_options &options, Channel &references) { 234 | int offset=0; 235 | int oy = y << ch.vshift; 236 | // for (int j=i-1; j>=image.nb_meta_channels && offset < options.max_properties; j--) { 237 | for (int j=i-1; j>=0 && offset < options.max_properties; j--) { 238 | if (image.channel[j].minval == image.channel[j].maxval) continue; 239 | if (image.channel[j].hshift < 0) continue; 240 | int ry = oy >> image.channel[j].vshift; 241 | if (ry >= image.channel[j].h) ry = image.channel[j].h-1; 242 | if (ch.hshift == image.channel[j].hshift && ch.w <= image.channel[j].w) 243 | for (int x=0; x> ch.hshift; 250 | int x=0, rx=0; 251 | pixel_type v; 252 | if (stepsize == 2) 253 | for (; rx> image.channel[j].hshift; 281 | if (rx >= image.channel[j].w) rx = image.channel[j].w-1; 282 | pixel_type v = image.channel[j].value_nocheck(ry,rx); 283 | references.value(x,offset) = fooabs(v); 284 | references.value(x,offset+1) = slog(v); 285 | } 286 | 287 | offset += 2; 288 | } 289 | } 290 | 291 | 292 | inline pixel_type predict_and_compute_properties_with_precomputed_reference(Properties &p, const Channel &ch, int x, int y, int predictor, const Image &image, int i, fuif_options &options, const Channel &references) ATTRIBUTE_HOT; 293 | inline pixel_type predict_and_compute_properties_with_precomputed_reference(Properties &p, const Channel &ch, int x, int y, int predictor, const Image &image, int i, fuif_options &options, const Channel &references) { 294 | int offset; 295 | for (offset=0; offset predictor; 46 | Image heatmap; 47 | }; 48 | const struct fuif_options default_fuif_options { 49 | .preview = -1, 50 | .identify = false, 51 | .nb_repeats = 0.5, 52 | .max_dist = 0, 53 | .max_properties = 12, 54 | .maniac_cutoff = 6, 55 | .maniac_alpha = 0x0d000000, 56 | .compress = true, 57 | .max_group = -1, 58 | .debug = false, 59 | }; 60 | 61 | void fuif_prepare_encode(Image &image, fuif_options &options); 62 | 63 | template 64 | bool fuif_encode(IO& io, const Image &image, fuif_options &options); 65 | 66 | bool fuif_encode_file(const char * filename, const Image &image, fuif_options &options); 67 | 68 | template 69 | bool fuif_decode(IO& io, Image &image, fuif_options options=default_fuif_options); 70 | 71 | bool fuif_decode_file(const char * filename, Image &image, fuif_options options=default_fuif_options); 72 | -------------------------------------------------------------------------------- /export/write_pam.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | #include "../image/image.h" 27 | 28 | 29 | bool write_PAM_file(const char *output_image, const Image &image) { 30 | 31 | int nb_channels = image.channel.size(); 32 | bool debugstuff = false; 33 | 34 | if (nb_channels > ((image.colormodel & 48) == 16 ? 5 : 4)) { 35 | e_printf("Too many channels. Saving just the first 3 channels (instead of %i)\n",nb_channels); 36 | nb_channels = 3; // just use the first 3. debug stuff 37 | debugstuff = true; 38 | } 39 | if (nb_channels == 0) { 40 | e_printf("Cannot save a zero-channel 'image' as PAM.\n"); 41 | return false; 42 | } 43 | 44 | int w = image.channel[0].w; 45 | int h = image.channel[0].h; 46 | if (image.w < w) w = image.w; 47 | if (image.h < h) h = image.h; 48 | for (int i=1; i image.nb_channels) {debugstuff=true; nb_channels=1;} 55 | 56 | FILE *fp = fopen(output_image,"wb"); 57 | if (!fp) { 58 | return false; 59 | } 60 | 61 | 62 | int components=3; 63 | bool cmyk = false; 64 | if ((image.colormodel & 48) == 0) { // RGB 65 | if (nb_channels == 4) components=4; 66 | } else if ((image.colormodel & 48) == 16) { // CMYK 67 | if (nb_channels == 5) components=4; 68 | if (nb_channels > 3) cmyk = true; 69 | } 70 | if (image.colormodel != 0 && image.colormodel != 16) { 71 | v_printf(1,"Warning: saving raw pixel data without a color profile, even though the image is not sRGB.\n"); 72 | } 73 | if (nb_channels == 1) components = 1; 74 | if (nb_channels == 2) components = 2; 75 | 76 | int bit_depth = 8, bytes_per_value=1; 77 | if (image.channel[0].minval < 0) {bit_depth = 8; bytes_per_value=1;} // this is just for debugging things 78 | 79 | 80 | if (image.maxval > 255) {bit_depth = 16; bytes_per_value=2;} 81 | if (image.maxval > 65535) { e_printf("Warning: cannot save as PAM since bit depth is higher than 16-bit. Doing it anyway.\n");} 82 | 83 | if (image.nb_channels == 0) { 84 | // visualize signed stuff 85 | int range = image.channel[0].maxval - image.channel[0].minval; 86 | v_printf(3,"Visualizing %ix%i channel with range %i..%i\n",w,h,image.channel[0].minval,image.channel[0].maxval); 87 | if (range > 255) {bit_depth = 16; bytes_per_value=2;} 88 | if (image.real_nb_channels == 0) { 89 | fprintf(fp,"P6\n%u %u\n%i\n", w, h, 255); 90 | for (int y = 0; y < h; y++) { 91 | for (int x = 0; x < w; x++) { 92 | for (int c=0; c 1) fputc((image.channel[c].value(y,x) - image.channel[0].minval) >> 8,fp); 106 | fputc((image.channel[c].value(y,x) - image.channel[0].minval) & 0xFF,fp); 107 | } 108 | } 109 | } 110 | } 111 | fclose(fp); 112 | return 0; 113 | } 114 | 115 | if (components == 1) fprintf(fp,"P5\n%u %u\n%i\n", w, h, image.maxval); 116 | if (components == 2) fprintf(fp,"P7\nWIDTH %u\nHEIGHT %u\nDEPTH 2\nMAXVAL %i\nTUPLTYPE GRAYSCALE_ALPHA\nENDHDR\n", w, h, image.maxval); 117 | if (components == 3) fprintf(fp,"P6\n%u %u\n%i\n", w, h, image.maxval); 118 | if (components == 4) fprintf(fp,"P7\nWIDTH %u\nHEIGHT %u\nDEPTH 4\nMAXVAL %i\nTUPLTYPE RGB_ALPHA\nENDHDR\n", w, h, image.maxval); 119 | v_printf(10,"Writing %i-channel PAM file (range %i..%i)\n",components,image.minval,image.maxval); 120 | 121 | if (cmyk) { 122 | v_printf(1,"Warning: converting CMYK image to RGB in order to save it as PAM.\n"); 123 | for (int y = 0; y < h; y++) { 124 | for (int x = 0; x < w; x++) { 125 | for (int c=0; c<3; c++) { 126 | if (bytes_per_value > 1) fputc(CLAMP( (image.maxval-image.channel[c].value_nocheck(y,x))*image.channel[3].value_nocheck(y,x)/image.maxval,image.minval,image.maxval) >> 8,fp); 127 | fputc(CLAMP( (image.maxval-image.channel[c].value_nocheck(y,x))*image.channel[3].value_nocheck(y,x)/image.maxval,image.minval,image.maxval) & 0xFF,fp); 128 | } 129 | if (nb_channels == 5) { 130 | if (bytes_per_value > 1) fputc(CLAMP(image.channel[4].value_nocheck(y,x),image.minval,image.maxval) >> 8,fp); 131 | fputc(CLAMP(image.channel[4].value_nocheck(y,x),image.minval,image.maxval) & 0xFF,fp); 132 | } 133 | } 134 | } 135 | } else { 136 | if (bytes_per_value > 1) { 137 | for (int y = 0; y < h; y++) { 138 | for (int x = 0; x < w; x++) { 139 | for (int c=0; c> 8,fp); 141 | fputc(CLAMP(image.channel[c].value_nocheck(y,x),image.minval,image.maxval) & 0xFF,fp); 142 | } 143 | } 144 | } 145 | } else { 146 | if (components == 3) { 147 | for (int y = 0; y < h; y++) { 148 | for (int x = 0; x < w; x++) { 149 | fputc(CLAMP(image.channel[0].value_nocheck(y,x),image.minval,image.maxval) & 0xFF,fp); 150 | fputc(CLAMP(image.channel[1].value_nocheck(y,x),image.minval,image.maxval) & 0xFF,fp); 151 | fputc(CLAMP(image.channel[2].value_nocheck(y,x),image.minval,image.maxval) & 0xFF,fp); 152 | } 153 | } 154 | } else { 155 | for (int y = 0; y < h; y++) { 156 | for (int x = 0; x < w; x++) { 157 | for (int c=0; c ((image.colormodel & 48) == 16 ? 5 : 4)) { 36 | e_printf("Too many channels. Saving just the first 3 channels (instead of %i)\n",nb_channels); 37 | nb_channels = 3; // just use the first 3. debug stuff 38 | debugstuff = true; 39 | } 40 | if (nb_channels == 0) { 41 | e_printf("Cannot save a zero-channel 'image' as PNG.\n"); 42 | return false; 43 | } 44 | 45 | 46 | int w = image.channel[0].w; 47 | int h = image.channel[0].h; 48 | if (image.w < w) w = image.w; 49 | if (image.h < h) h = image.h; 50 | for (int i=1; i image.nb_channels) {debugstuff=true; nb_channels=1;} 57 | 58 | if (w > 0x7fffffffL || h > 0x7fffffffL) { 59 | printf("Image too large to be saved as PNG.\n"); 60 | return false; 61 | } 62 | 63 | FILE *fp = fopen(output_image,"wb"); 64 | if (!fp) { 65 | return false; 66 | } 67 | png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,(png_voidp) NULL,NULL,NULL); 68 | if (!png_ptr) { 69 | fclose(fp); 70 | return false; 71 | } 72 | 73 | png_infop info_ptr = png_create_info_struct(png_ptr); 74 | if (!info_ptr) { 75 | png_destroy_write_struct(&png_ptr,(png_infopp) NULL); 76 | fclose(fp); 77 | return false; 78 | } 79 | 80 | png_init_io(png_ptr,fp); 81 | png_set_user_limits(png_ptr, 0x7fffffffL, 0x7fffffffL); 82 | // png_set_filter(png_ptr,0,PNG_FILTER_PAETH); 83 | // png_set_compression_level(png_ptr,Z_BEST_COMPRESSION); 84 | 85 | 86 | int colortype=PNG_COLOR_TYPE_RGB; 87 | bool cmyk = false; 88 | if ((image.colormodel & 48) == 0) { // RGB 89 | if (nb_channels == 4) colortype=PNG_COLOR_TYPE_RGB_ALPHA; 90 | } else if ((image.colormodel & 48) == 16) { // CMYK 91 | if (nb_channels == 5) colortype=PNG_COLOR_TYPE_RGB_ALPHA; 92 | if (nb_channels > 3) cmyk = true; 93 | } 94 | 95 | if (nb_channels == 1) colortype=PNG_COLOR_TYPE_GRAY; 96 | if (nb_channels == 2) colortype=PNG_COLOR_TYPE_GRAY_ALPHA; 97 | int bit_depth = 8, bytes_per_value=1; 98 | if (image.channel[0].minval < 0) {bit_depth = 8; bytes_per_value=1;} // this is just for debugging things 99 | 100 | if (image.maxval > 255) {bit_depth = 16; bytes_per_value=2;} 101 | if (image.maxval > 65535) { e_printf("Cannot save as PNG since bit depth is higher than 16-bit.\n"); return false; } 102 | 103 | if (image.channel[0].maxval > 255 && debugstuff && nb_channels == 1) {colortype=PNG_COLOR_TYPE_RGB; nb_channels=3;} 104 | 105 | int shift = 0; 106 | // if (image.channel[0].maxval == 2048) shift = 5; // could use something like this to debug DCT values 107 | 108 | png_set_IHDR(png_ptr,info_ptr,w,h,bit_depth,colortype,PNG_INTERLACE_NONE,PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT); 109 | 110 | png_write_info(png_ptr,info_ptr); 111 | 112 | png_bytep row = (png_bytep) png_malloc(png_ptr,nb_channels * bytes_per_value * w); 113 | 114 | v_printf(10,"Writing PNG file (range %i..%i)\n",image.minval,image.maxval); 115 | if (image.channel[0].maxval > 255 && debugstuff) { 116 | v_printf(10,"Writing PNG file with false colors for debug purposes\n"); 117 | for (size_t r = 0; r < (size_t) h; r++) { 118 | for (size_t c = 0; c < (size_t) w; c++) { 119 | row[c * 3 + 0] = (png_byte) (CLAMP((image.channel[0].value(r,c)%10)*26,0,255)); 120 | row[c * 3 + 1] = (png_byte) (CLAMP(image.channel[0].value(r,c)/10,0,255)); 121 | row[c * 3 + 2] = (png_byte) (CLAMP(image.channel[0].value(r,c)/500,0,255)); 122 | } 123 | png_write_row(png_ptr,row); 124 | } 125 | } else if (false && image.channel[0].minval < 0) { 126 | v_printf(10,"Writing PNG file with negative pixels\n"); 127 | // visualize the negative pixels somehow, for debugging. 128 | for (size_t r = 0; r < (size_t) h; r++) { 129 | for (size_t c = 0; c < (size_t) w; c++) { 130 | for (int p=0; p> 8); 160 | row[c * nb_channels * 2 + 2*p + 1] = (png_byte) ((CLAMP(image.channel[p].value(r,c),image.minval,image.maxval)<> 8); 167 | row[c * nb_channels * 2 + 2*p + 1] = (png_byte) ((image.channel[p].value(r,c)) & 0xff); 168 | } 169 | } 170 | } 171 | } 172 | png_write_row(png_ptr,row); 173 | } 174 | 175 | png_free(png_ptr,row); 176 | png_write_end(png_ptr,info_ptr); 177 | png_destroy_write_struct(&png_ptr,&info_ptr); 178 | fclose(fp); 179 | return 0; 180 | } 181 | -------------------------------------------------------------------------------- /export/write_yuv.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | #include "../image/image.h" 27 | 28 | 29 | bool write_YUV_file(const char *output_image, const Image &image) { 30 | 31 | int nb_channels = image.channel.size(); 32 | if (nb_channels != 3 || image.transform.size() != 2 || image.transform[0].ID != TRANSFORM_YCbCr || image.transform[1].ID != TRANSFORM_ChromaSubsample 33 | || image.channel[1].hshift != 1 || image.channel[2].hshift != 1) { 34 | e_printf("Image is not YCbCr 4:2:0, not saving as .yuv.\n"); 35 | return false; 36 | } 37 | 38 | FILE *fp = fopen(output_image,"wb"); 39 | if (!fp) { 40 | return false; 41 | } 42 | if (image.colormodel != 0) { 43 | v_printf(1,"Warning: saving as .yuv, even though the image is not sRGB.\n"); 44 | } 45 | 46 | for (int c = 0; c < 3; c++) { 47 | for (int y = 0; y < image.channel[c].h; y++) { 48 | for (int x = 0; x < image.channel[c].w; x++) { 49 | fputc(CLAMP(image.channel[c].value(y,x),image.minval,image.maxval) & 0xFF,fp); 50 | if (image.maxval > 255) 51 | fputc((CLAMP(image.channel[c].value(y,x),image.minval,image.maxval)>>8) & 0xFF,fp); 52 | } 53 | } 54 | } 55 | 56 | fclose(fp); 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /fileio.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | FUIF - FREE UNIVERSAL IMAGE FORMAT 4 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 5 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 6 | 7 | Permission is hereby granted, free of charge, to any person obtaining a copy 8 | of this software and associated documentation files (the "Software"), to deal 9 | in the Software without restriction, including without limitation the rights 10 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | copies of the Software, and to permit persons to whom the Software is 12 | furnished to do so, subject to the following conditions: 13 | 14 | The above copyright notice and this permission notice shall be included in all 15 | copies or substantial portions of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | SOFTWARE. 24 | 25 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 26 | 27 | #pragma once 28 | 29 | #include 30 | #include 31 | #include 32 | 33 | class FileIO 34 | { 35 | private: 36 | FILE *file; 37 | const char *name; 38 | public: 39 | // prevent copy 40 | FileIO(const FileIO&) = delete; 41 | void operator=(const FileIO&) = delete; 42 | // prevent move, for now 43 | FileIO(FileIO&&) = delete; 44 | void operator=(FileIO&&) = delete; 45 | 46 | const int EOS = EOF; 47 | 48 | FileIO(FILE* fil, const char *aname) : file(fil), name(aname) { } 49 | ~FileIO() { 50 | if (file) fclose(file); 51 | } 52 | void flush() { 53 | fflush(file); 54 | } 55 | bool isEOF() { 56 | return feof(file); 57 | } 58 | long ftell() { 59 | return ::ftell(file); 60 | } 61 | int get_c() { 62 | return fgetc(file); 63 | } 64 | char * gets(char *buf, int n) { 65 | return fgets(buf, n, file); 66 | } 67 | int fputs(const char *s) { 68 | return ::fputs(s, file); 69 | } 70 | int fputc(int c) { 71 | return ::fputc(c, file); 72 | } 73 | void fseek(long offset, int where) { 74 | ::fseek(file, offset,where); 75 | } 76 | const char* getName() const { 77 | return name; 78 | } 79 | }; 80 | 81 | /*! 82 | * Read-only IO interface for a constant memory block 83 | */ 84 | class BlobReader 85 | { 86 | private: 87 | const uint8_t* data; 88 | size_t data_array_size; 89 | size_t seek_pos; 90 | public: 91 | const int EOS = -1; 92 | 93 | BlobReader(const uint8_t* _data, size_t _data_array_size) 94 | : data(_data) 95 | , data_array_size(_data_array_size) 96 | , seek_pos(0) 97 | { 98 | } 99 | 100 | bool isEOF() const { 101 | return seek_pos >= data_array_size; 102 | } 103 | long ftell() const { 104 | return seek_pos; 105 | } 106 | int get_c() { 107 | if(seek_pos >= data_array_size) 108 | return EOS; 109 | return data[seek_pos++]; 110 | } 111 | char * gets(char *buf, int n) { 112 | int i = 0; 113 | const int max_write = n-1; 114 | while(seek_pos < data_array_size && i < max_write) 115 | buf[i++] = data[seek_pos++]; 116 | buf[n-1] = '\0'; 117 | 118 | if(i < max_write) 119 | return 0; 120 | else 121 | return buf; 122 | } 123 | int fputc(int c) { 124 | // cannot write on const memory 125 | return EOS; 126 | } 127 | void fseek(long offset, int where) { 128 | switch(where) { 129 | case SEEK_SET: 130 | seek_pos = offset; 131 | break; 132 | case SEEK_CUR: 133 | seek_pos += offset; 134 | break; 135 | case SEEK_END: 136 | seek_pos = long(data_array_size) + offset; 137 | break; 138 | } 139 | } 140 | static const char* getName() { 141 | return "BlobReader"; 142 | } 143 | }; 144 | 145 | /*! 146 | * IO interface for a growable memory block 147 | */ 148 | class BlobIO 149 | { 150 | private: 151 | uint8_t* data; 152 | // keeps track how large the array really is 153 | // HINT: should only be used in the grow() function 154 | size_t data_array_size; 155 | // keeps track how many bytes were written 156 | size_t bytes_used; 157 | size_t seek_pos; 158 | 159 | void grow(size_t necessary_size) { 160 | if(necessary_size < data_array_size) 161 | return; 162 | 163 | size_t new_size = necessary_size; 164 | // start with reasonably large array 165 | if(new_size < 4096) 166 | new_size = 4096; 167 | 168 | if(new_size < data_array_size * 3 / 2) 169 | new_size = data_array_size * 3 / 2; 170 | uint8_t* new_data = new uint8_t[new_size]; 171 | 172 | memcpy(new_data, data, bytes_used); 173 | // if seek_pos has been moved beyond the written bytes, 174 | // fill the empty space with zeroes 175 | for(size_t i = bytes_used; i < seek_pos; ++i) 176 | new_data[i] = 0; 177 | delete [] data; 178 | data = new_data; 179 | std::swap(data_array_size, new_size); 180 | } 181 | public: 182 | const int EOS = -1; 183 | 184 | BlobIO() 185 | : data(0) 186 | , data_array_size(0) 187 | , bytes_used(0) 188 | , seek_pos(0) 189 | { 190 | } 191 | 192 | ~BlobIO() { 193 | delete [] data; 194 | } 195 | 196 | uint8_t* release(size_t* array_size) 197 | { 198 | uint8_t* ptr = data; 199 | *array_size = bytes_used; 200 | 201 | data = 0; 202 | data_array_size = 0; 203 | bytes_used = 0; 204 | seek_pos = 0; 205 | return ptr; 206 | } 207 | 208 | static void flush() { 209 | // nothing to do 210 | } 211 | bool isEOF() const { 212 | return seek_pos >= bytes_used; 213 | } 214 | int ftell() const { 215 | return seek_pos; 216 | } 217 | int get_c() { 218 | if(seek_pos >= bytes_used) 219 | return EOS; 220 | return data[seek_pos++]; 221 | } 222 | char * gets(char *buf, int n) { 223 | int i = 0; 224 | const int max_write = n-1; 225 | while(seek_pos < bytes_used && i < max_write) 226 | buf[i++] = data[seek_pos++]; 227 | buf[n-1] = '\0'; 228 | 229 | if(i < max_write) 230 | return 0; 231 | else 232 | return buf; 233 | } 234 | int fputs(const char *s) { 235 | size_t i = 0; 236 | // null-terminated string 237 | while(s[i]) 238 | { 239 | grow(seek_pos + 1); 240 | data[seek_pos++] = s[i++]; 241 | if(bytes_used < seek_pos) 242 | bytes_used = seek_pos+1; 243 | } 244 | return 0; 245 | } 246 | int fputc(int c) { 247 | grow(seek_pos + 1); 248 | 249 | data[seek_pos++] = static_cast(c); 250 | if(bytes_used < seek_pos) 251 | bytes_used = seek_pos+1; 252 | return c; 253 | } 254 | void fseek(long offset, int where) { 255 | switch(where) { 256 | case SEEK_SET: 257 | seek_pos = offset; 258 | break; 259 | case SEEK_CUR: 260 | seek_pos += offset; 261 | break; 262 | case SEEK_END: 263 | seek_pos = long(bytes_used) + offset; 264 | break; 265 | } 266 | } 267 | static const char* getName() { 268 | return "BlobIO"; 269 | } 270 | }; 271 | 272 | /*! 273 | * Dummy IO 274 | */ 275 | class DummyIO 276 | { 277 | public: 278 | const int EOS = -1; 279 | 280 | DummyIO() {} 281 | 282 | static void flush() { 283 | // nothing to do 284 | } 285 | bool isEOF() const { 286 | return false; 287 | } 288 | int ftell() const { 289 | return -1; 290 | } 291 | int get_c() { 292 | return 0; 293 | } 294 | char * gets(char *buf, int n) { 295 | return 0; 296 | } 297 | int fputs(const char *s) { 298 | return 0; 299 | } 300 | int fputc(int c) { 301 | return c; 302 | } 303 | void fseek(long offset, int where) { 304 | } 305 | static const char* getName() { 306 | return "DummyIO"; 307 | } 308 | }; 309 | 310 | -------------------------------------------------------------------------------- /fuifplay.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | 26 | // somewhat based on https://github.com/lecram/gifdec/blob/master/example.c 27 | 28 | #include 29 | 30 | #include 31 | 32 | #include "config.h" 33 | #include "image/image.h" 34 | #include "encoding/encoding.h" 35 | #include 36 | 37 | int main(int argc, char *argv[]) { 38 | 39 | static struct option optlist[] = { 40 | {"help", 0, NULL, 'h'}, 41 | {"verbose", 0, NULL, 'v'}, 42 | {"responsive", 1, NULL, 'R'}, 43 | {0,0,0,0} 44 | }; 45 | 46 | bool showhelp=false; 47 | int responsive=-1; 48 | int c,i; 49 | while ((c = getopt_long (argc, argv, "hvR:", optlist, &i)) != -1) { 50 | switch (c) { 51 | case 'v': increase_verbosity(); break; 52 | case 'R': responsive = atoi(optarg); break; 53 | case 'h': 54 | default: showhelp=true; break; 55 | } 56 | } 57 | if (argc-optind < 1 || showhelp) { 58 | v_printf(1,"Usage: %s [options]\n", argv[0]); 59 | v_printf(1," -h, --help show help\n"); 60 | v_printf(1," -v, --verbose increase verbosity (multiple -v for more output)\n"); 61 | v_printf(1," -R, --responsive=K -1=full image (default), 0=LQIP, 1=(1:16), 2=(1:8), 3=(1:4), 4=(1:2)\n"); 62 | return 1; 63 | } 64 | argc -= optind; 65 | argv += optind; 66 | 67 | fuif_options options = default_fuif_options; 68 | if (responsive < -1 || responsive > 4) { 69 | e_printf("Invalid value for -R option (range: -1..4)\n"); 70 | return 1; 71 | } 72 | options.preview = responsive; 73 | 74 | SDL_Window *window; 75 | SDL_Renderer *renderer; 76 | SDL_Surface *surface; 77 | SDL_Texture *texture; 78 | SDL_Event event; 79 | 80 | char title[32] = {0}; 81 | 82 | char * sp; 83 | Uint32 pixel; 84 | int ret, paused, quit; 85 | Uint32 t0, t1, delay, delta; 86 | 87 | 88 | Image decoded; 89 | if (fuif_decode_file(argv[0],decoded,options)) { 90 | decoded.undo_transforms(); 91 | } else { 92 | e_printf("Could not decode %s\n",argv[0]); 93 | return -1; 94 | } 95 | 96 | int w = decoded.w; 97 | int h = decoded.h / decoded.nb_frames; 98 | int frames = decoded.nb_frames; 99 | int cf = 0; 100 | 101 | printf("Decoded %ix%i FUIF with %i frames, %i fps\n",w,h,frames,decoded.den); 102 | 103 | if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_TIMER) != 0) { 104 | SDL_Log("Unable to initialize SDL: %s", SDL_GetError()); 105 | return 1; 106 | } 107 | SDL_EventState(SDL_MOUSEMOTION,SDL_IGNORE); 108 | 109 | snprintf(title, sizeof(title) - 1, "FUIF (%dx%d, %d frames)", w, h, frames); 110 | window = SDL_CreateWindow(title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h, SDL_WINDOW_RESIZABLE); 111 | renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 112 | if (!window || !renderer) { 113 | SDL_Log("Couldn't create window or renderer: %s", SDL_GetError()); 114 | return 1; 115 | } 116 | SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0x00); 117 | SDL_RenderClear(renderer); 118 | SDL_RenderPresent(renderer); 119 | surface = SDL_CreateRGBSurface(0, w, h, 32, 0x000000FF,0x0000FF00,0x00FF0000,0xFF000000); 120 | if (!surface) { 121 | SDL_Log("SDL_CreateRGBSurface() failed: %s", SDL_GetError()); 122 | return 1; 123 | } 124 | paused = 0; 125 | quit = 0; 126 | bool alpha=(decoded.nb_channels > 3); 127 | if (decoded.den == 0 || frames < 2) decoded.den = 2; // update twice per second if it's not an animation 128 | while (1) { 129 | while (SDL_PollEvent(&event) && !quit) { 130 | if (event.type == SDL_QUIT) 131 | quit = 1; 132 | if (event.type == SDL_KEYDOWN) { 133 | if (event.key.keysym.sym == SDLK_q) 134 | quit = 1; 135 | else if (event.key.keysym.sym == SDLK_SPACE) 136 | paused = !paused; 137 | } 138 | } 139 | if (quit) break; 140 | if (paused) { 141 | SDL_Delay(10); 142 | continue; 143 | } 144 | t0 = SDL_GetTicks(); 145 | SDL_LockSurface(surface); 146 | sp = (char*) surface->pixels; 147 | if (alpha) 148 | for (i = 0; i < h; i++) { 149 | for (int j = 0; j < w; j++) { 150 | int a = decoded.channel[3].value(i+h*cf,j); 151 | int b = ( (i/10 + j/10)&1 ? 0x60 : 0xA0 ); // checkerboard pattern 152 | sp[4*j+0] = (decoded.channel[0].value(i+h*cf,j) * a + b * (255-a))>>8; 153 | sp[4*j+1] = (decoded.channel[1].value(i+h*cf,j) * a + b * (255-a))>>8; 154 | sp[4*j+2] = (decoded.channel[2].value(i+h*cf,j) * a + b * (255-a))>>8; 155 | sp[4*j+3] = 255; 156 | } 157 | sp += surface->pitch; 158 | } 159 | else 160 | for (i = 0; i < h; i++) { 161 | for (int j = 0; j < w; j++) { 162 | sp[4*j+0] = decoded.channel[0].value(i+h*cf,j); 163 | sp[4*j+1] = decoded.channel[1].value(i+h*cf,j); 164 | sp[4*j+2] = decoded.channel[2].value(i+h*cf,j); 165 | sp[4*j+3] = 255; 166 | } 167 | sp += surface->pitch; 168 | } 169 | SDL_UnlockSurface(surface); 170 | texture = SDL_CreateTextureFromSurface(renderer, surface); 171 | SDL_RenderCopy(renderer, texture, NULL, NULL); 172 | SDL_DestroyTexture(texture); 173 | SDL_RenderPresent(renderer); 174 | t1 = SDL_GetTicks(); 175 | delta = t1 - t0; 176 | delay = (decoded.num.size() ? decoded.num[cf] : 1) * 1000 / decoded.den; 177 | delay = delay > delta ? delay - delta : 1; 178 | SDL_Delay(delay); 179 | cf++; 180 | if (cf >= frames) cf = 0; 181 | } 182 | SDL_FreeSurface(surface); 183 | SDL_DestroyRenderer(renderer); 184 | SDL_DestroyWindow(window); 185 | SDL_Quit(); 186 | return 0; 187 | } 188 | -------------------------------------------------------------------------------- /image/image.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #include "image.h" 26 | #include "../transform/transform.h" 27 | #include "../io.h" 28 | 29 | // colormodel: 6-bit number abcdefg 30 | // a (64) : custom profile? (TODO: add some mechanism to include an ICC profile) 31 | // bc (32/16) : 00 = RGB, 01 = CMYK, 10 = CIE, 11 = reserved 32 | // defg : 4-bit code for some standard/common color profile 33 | 34 | // TODO: add standard color profiles, write them in image export and detect them in image import 35 | 36 | const char * colormodel_name(int colormodel, int nb_channels) { 37 | if (nb_channels == 1) return "Grayscale"; 38 | else if (nb_channels == 2) return "Grayscale+alpha"; 39 | 40 | switch (colormodel>>4) { 41 | case 7: return "Custom other"; 42 | case 6: return "Custom CIE"; 43 | case 5: return "Custom CMYK"; 44 | case 4: return "Custom RGB"; 45 | case 3: return "[RESERVED]"; 46 | case 2: 47 | if (nb_channels == 3) { 48 | if (colormodel & 1) return "CIEXYZ"; 49 | else return "CIELAB"; 50 | } else if (nb_channels == 4) { 51 | if (colormodel & 1) return "CIEXYZ+alpha"; 52 | else return "CIELAB+alpha"; 53 | } 54 | case 1: 55 | if (nb_channels == 3) return "CMY"; 56 | else if (nb_channels == 4) return "CMYK"; 57 | else if (nb_channels == 5) return "CMYK+alpha"; 58 | else return "CMYK+"; 59 | 60 | case 0: 61 | default: 62 | if (nb_channels == 3) return "RGB"; 63 | else if (nb_channels == 4) return "RGBA"; 64 | else return "RGB+"; 65 | } 66 | } 67 | const char * colorprofile_name(int colormodel) { 68 | if (colormodel>>4) { 69 | return ""; // TODO: define some common non-RGB profiles 70 | } else { 71 | // common RGB profiles 72 | switch (colormodel & 0xF) { 73 | case 1: return " (DCI-P3)"; 74 | case 2: return " (Rec.2020)"; 75 | case 3: return " (Adobe RGB 1998)"; 76 | case 4: return " (ProPhoto)"; 77 | case 0: 78 | default: return " (sRGB)"; 79 | } 80 | } 81 | } 82 | 83 | void Channel::actual_minmax(pixel_type *min, pixel_type *max) const { 84 | pixel_type realmin=LARGEST_VAL; 85 | pixel_type realmax=SMALLEST_VAL; 86 | for (int i=0; i realmax) realmax = data[i]; 89 | } 90 | *min = realmin; 91 | *max = realmax; 92 | } 93 | 94 | void Image::undo_transforms(int keep) { 95 | while ( transform.size() > keep ) { 96 | Transform t = transform.back(); 97 | v_printf(4,"Undoing transform %s\n",t.name()); 98 | bool result = t.apply(*this, true); 99 | if (result == false) { 100 | e_printf("Error while undoing transform %s.\n",t.name()); 101 | error = true; 102 | return; 103 | } 104 | v_printf(8,"Undoing transform %s: done\n",t.name()); 105 | transform.pop_back(); 106 | } 107 | if (!keep) { // clamp the values to the valid range (lossy compression can produce values outside the range) 108 | for (int i=0; i 28 | #include 29 | #include 30 | #include 31 | 32 | #include "../util.h" 33 | #include 34 | 35 | typedef int16_t pixel_type; // enough for up to 14-bit with YCoCg/Squeeze (I think); enough for up to 10-bit if DCT is added (I think) 36 | #define LARGEST_VAL 0x7FFF 37 | #define SMALLEST_VAL 0x8001 38 | 39 | //typedef int32_t pixel_type; // can use int16_t if it's only for 8-bit images. Need some wiggle room for YCoCg / DCT / Squeeze etc 40 | 41 | // largest possible pixel value (2147483647) 42 | //#define LARGEST_VAL 0x7FFFFFFF 43 | 44 | // smallest possible pixel value (-2147483647) 45 | //#define SMALLEST_VAL 0x80000001 46 | 47 | 48 | 49 | #define RESPONSIVE_SIZE(n) ((32 >> n)) 50 | 51 | 52 | // TODO: add two pixels of padding on the left and top, and one pixel on the right and bottom, 53 | // and get rid of all edge cases in the context properties / predictors / etc 54 | class Channel { 55 | public: 56 | std::vector data; 57 | int w, h; 58 | pixel_type minval, maxval; // range 59 | mutable pixel_type zero; // should be zero if zero is a valid value for this channel; the valid value closest to zero otherwise 60 | int q; // quantization factor 61 | int hshift, vshift; // w ~= image.w >> hshift; h ~= image.h >> vshift 62 | int hcshift, vcshift; // cumulative, i.e. when decoding up to this point, we have data available with these shifts (for this component) 63 | int component; // this channel contains (some) data of this component (-1 = bug/unknown) 64 | Channel(int iw, int ih, pixel_type iminval, pixel_type imaxval, int qf=1, int hsh=0, int vsh=0, int hcsh=0, int vcsh=0) : 65 | w(iw), h(ih), minval(iminval), maxval(imaxval), data(iw*ih,0), q(qf), hshift(hsh), vshift(vsh), hcshift(hcsh), vcshift(vcsh), component(-1) { setzero(); } 66 | Channel() : w(0), h(0), minval(0), maxval(0), zero(0), q(1), hshift(0), vshift(0), hcshift(0), vcshift(0), component(-1) { } 67 | 68 | void setzero() const { 69 | if (minval > 0) zero = minval; 70 | else if (maxval < 0) zero = maxval; 71 | else zero = 0; 72 | } 73 | void resize() { 74 | data.resize(w*h,zero); 75 | } 76 | void resize(int nw, int nh) { 77 | w=nw; h=nh; 78 | resize(); 79 | } 80 | 81 | pixel_type value_nocheck(int r, int c) const { return data[r*w+c]; } 82 | pixel_type value(int r, int c) const { if (r*w+c >= data.size()) return zero; 83 | assert(r*w+c >=0); assert(r*w+c < data.size()); return data[r*w+c]; } 84 | pixel_type &value(int r, int c) { if (r*w+c >= data.size()) return zero; 85 | assert(r*w+c >=0); assert(r*w+c < data.size()); return data[r*w+c]; } 86 | pixel_type repeating_edge_value(int r, int c) const { return value( (r<0?0:(r>=h?h-1:r)) , (c<0?0:(c>=w?w-1:c)) ); } 87 | pixel_type value(size_t i) const { assert(i < data.size()); return data[i]; } 88 | pixel_type &value(size_t i) { assert(i < data.size()); return data[i]; } 89 | 90 | void actual_minmax(pixel_type *min, pixel_type *max) const; 91 | }; 92 | 93 | class Transform; 94 | 95 | const char * colormodel_name(int colormodel, int nb_channels); 96 | const char * colorprofile_name(int colormodel); 97 | 98 | class Image { 99 | public: 100 | std::vector channel; // image data, transforms can dramatically change the number of channels and their semantics 101 | std::vector transform; // keeps track of the transforms that have been applied (and that have to be undone when rendering the image) 102 | 103 | int w,h; // actual dimensions of the image (channels may have different dimensions due to transforms like chroma subsampling and DCT) 104 | int nb_frames; // number of frames; multi-frame images are stored as a vertical filmstrip 105 | int den; // frames per second (denominator of frame duration in seconds; default numerator is 1) 106 | std::vector num; // numerator of frame duration 107 | int loops; // 0=forever 108 | int minval,maxval; // actual (largest) range of the channels (actual ranges might be different due to transforms; after undoing transforms, might still be different due to lossy) 109 | int nb_channels; // actual number of distinct channels (after undoing all transforms except Palette; can be different from channel.size()) 110 | int real_nb_channels; // real number of channels (after undoing all transforms) 111 | int nb_meta_channels; // first few channels might contain things like palettes or compaction data that are not yet real image data 112 | int colormodel; // indicates one of the standard color models/spaces/profiles, or a custom one (in which case ICC profile should be provided) 113 | std::vector icc_profile; // icc profile blob 114 | int downscales[6]; // LQIP, 1:16, 1:8, 1:4, 1:2, 1:1 115 | bool error; // true if a fatal error occurred, false otherwise 116 | 117 | Image(int iw, int ih, int maxval, int nb_chans, int cm=0) : 118 | channel(nb_chans,Channel(iw,ih,0,maxval)), 119 | w(iw), h(ih), nb_frames(1), den(10), loops(0), minval(0), maxval(maxval), nb_channels(nb_chans), real_nb_channels(nb_chans), nb_meta_channels(0), colormodel(cm), error(false) { 120 | for (int i=0; i 29 | 30 | // TODO: read color profile and add it to the image (currently profiles are just silently stripped, which is of course wrong) 31 | 32 | struct jpegErrorManager { 33 | /* "public" fields */ 34 | struct jpeg_error_mgr pub; 35 | /* for return to caller */ 36 | jmp_buf setjmp_buffer; 37 | }; 38 | char jpegLastErrorMsg[JMSG_LENGTH_MAX]; 39 | void jpegErrorExit (j_common_ptr cinfo) 40 | { 41 | /* cinfo->err actually points to a jpegErrorManager struct */ 42 | jpegErrorManager* myerr = (jpegErrorManager*) cinfo->err; 43 | /* note : *(cinfo->err) is now equivalent to myerr->pub */ 44 | 45 | /* output_message is a method to print an error message */ 46 | /*(* (cinfo->err->output_message) ) (cinfo);*/ 47 | 48 | /* Create the message */ 49 | ( *(cinfo->err->format_message) ) (cinfo, jpegLastErrorMsg); 50 | 51 | /* Jump to the setjmp point */ 52 | longjmp(myerr->setjmp_buffer, 1); 53 | 54 | } 55 | 56 | Image read_JPEG_file(const char *input_image) { 57 | 58 | FILE *fp = fopen(input_image,"rb"); 59 | if (!fp) return Image(); 60 | 61 | struct jpeg_decompress_struct cinfo; 62 | 63 | jpegErrorManager jerr; 64 | cinfo.err = jpeg_std_error(&jerr.pub); 65 | jerr.pub.error_exit = jpegErrorExit; 66 | /* Establish the setjmp return context for my_error_exit to use. */ 67 | if (setjmp(jerr.setjmp_buffer)) { 68 | /* If we get here, the JPEG code has signaled an error. */ 69 | v_printf(10, "libjpeg: %s\n",jpegLastErrorMsg); 70 | jpeg_destroy_decompress(&cinfo); 71 | fclose(fp); 72 | return Image(); 73 | } 74 | 75 | jpeg_create_decompress(&cinfo); 76 | jpeg_stdio_src(&cinfo, fp); 77 | (void) jpeg_read_header(&cinfo, TRUE); 78 | 79 | 80 | jvirt_barray_ptr *coeffs_array = jpeg_read_coefficients(&cinfo); 81 | 82 | int nbcomp = cinfo.num_components; 83 | int w = cinfo.image_width; 84 | int h = cinfo.image_height; 85 | int bitdepth = cinfo.data_precision; 86 | 87 | Image image; 88 | image.w = w; 89 | image.h = h; 90 | image.nb_channels = nbcomp; 91 | image.real_nb_channels = nbcomp; 92 | image.minval = 0; 93 | image.maxval = (1<h_samp_factor; 103 | int srv = compptr->v_samp_factor; 104 | if (srh > max_factor_h) max_factor_h = srh; 105 | if (srv > max_factor_v) max_factor_v = srv; 106 | } 107 | std::vector & p = subsampling.parameters; 108 | for (int ci=0; ci < nbcomp; ci++) { 109 | jpeg_component_info* compptr = cinfo.comp_info + ci; 110 | int srh = compptr->h_samp_factor; 111 | int srv = compptr->v_samp_factor; 112 | if (srh < max_factor_h || srv < max_factor_v) { 113 | p.push_back(ci); 114 | p.push_back(ci); 115 | p.push_back(max_factor_h/srh); 116 | p.push_back(max_factor_v/srv); 117 | } 118 | } 119 | if (p.size() >= 8) { 120 | if (p[2] == p[6] && p[3] == p[7] && p[4] == p[1]+1) { 121 | p[1] = p[4]; 122 | p.erase(p.begin()+4, p.end()); 123 | } 124 | if (p.size() == 4 && p[0] == 1 && p[1] == 2) { 125 | if (p[2] == 2 && p[3] == 2) { p[0] = 0; p.erase(p.begin()+1, p.end()); } // 4:2:0 126 | } 127 | } 128 | 129 | if (cinfo.jpeg_color_space == JCS_YCbCr || 130 | cinfo.jpeg_color_space == JCS_YCCK) image.transform.push_back(Transform(TRANSFORM_YCbCr)); 131 | 132 | if (p.size()) image.transform.push_back(subsampling); 133 | 134 | image.transform.push_back(Transform(TRANSFORM_DCT)); 135 | 136 | image.transform.push_back(Transform(TRANSFORM_QUANTIZE)); 137 | 138 | std::vector> ordering; 139 | std::vector comp; 140 | std::vector coeff; 141 | default_DCT_scanscript(nbcomp,ordering,comp,coeff); 142 | 143 | for (int i=0; i < 64*nbcomp; i++) { 144 | int bi=0; 145 | for (; bi<64 ; bi++) if (coeff[i]==jpeg_zigzag[bi]) break; 146 | int ci = comp[i]; 147 | jpeg_component_info* compptr = cinfo.comp_info + ci; 148 | int hib = compptr->height_in_blocks; 149 | int wib = compptr->width_in_blocks; 150 | int q = compptr->quant_table->quantval[bi]; 151 | v_printf(5,"Quantization for component %i, coefficient %i: %i\n",comp[i],coeff[i],q); 152 | 153 | Channel c(wib,hib,LARGEST_VAL,SMALLEST_VAL); 154 | c.q = q; 155 | c.component = ci; 156 | c.hshift = 3 + (compptr->h_samp_factor < max_factor_h ? 1 : 0); 157 | c.vshift = 3 + (compptr->v_samp_factor < max_factor_v ? 1 : 0); 158 | c.hcshift = dct_cshifts[coeff[i]]; 159 | c.vcshift = dct_cshifts[coeff[i]]; 160 | int zeroes=0; 161 | int nonzeroes=0; 162 | for (int by=0; by < hib; by++) { 163 | JBLOCKARRAY buffer = (cinfo.mem->access_virt_barray)((j_common_ptr)&cinfo, coeffs_array[ci], by, (JDIMENSION) 1, FALSE); 164 | for (int bx=0; bx < wib; bx++) { 165 | JCOEFPTR blockptr = buffer[0][bx]; 166 | c.value(by,bx) = blockptr[bi]; 167 | if (c.value(by,bx) > c.maxval) c.maxval = c.value(by,bx); 168 | if (c.value(by,bx) < c.minval) c.minval = c.value(by,bx); 169 | if (c.value(by,bx)) nonzeroes++; else zeroes++; 170 | } 171 | } 172 | v_printf(10,"Comp %i, coeff %i (natural %i) : %f %% zeroes (%i zeroes, %i nonzeroes)\n",ci,coeff[i],bi,100.0*zeroes/(zeroes+nonzeroes),zeroes,nonzeroes); 173 | image.channel.push_back(c); 174 | } 175 | 176 | jpeg_finish_decompress( &cinfo ); 177 | jpeg_destroy_decompress( &cinfo ); 178 | fclose(fp); 179 | 180 | image.error = false; 181 | 182 | 183 | return image; 184 | } 185 | -------------------------------------------------------------------------------- /import/read_pam.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | #include "../image/image.h" 27 | 28 | #define PPMREADBUFLEN 256 29 | 30 | 31 | // auxiliary function to read a non-zero number from a PNM header, ignoring whitespace and comments 32 | unsigned int read_pnm_int(FILE *fp, char* buf, char** t) { 33 | long result = strtol(*t,t,10); 34 | if (result == 0) { 35 | // no valid int found here, try next line 36 | do { 37 | *t = fgets(buf, PPMREADBUFLEN, fp); 38 | if ( *t == NULL ) {return false;} 39 | } while ( strncmp(buf, "#", 1) == 0 || strncmp(buf, "\n", 1) == 0); 40 | result = strtol(*t,t,10); 41 | if (result == 0) { e_printf("Invalid PNM file.\n"); fclose(fp); } 42 | } 43 | return result; 44 | } 45 | 46 | Image read_PAM_file(const char *filename) { 47 | FILE *fp = NULL; 48 | if (!strcmp(filename,"-")) fp = stdin; 49 | else fp = fopen(filename,"rb"); 50 | 51 | char buf[PPMREADBUFLEN], *t; 52 | if (!fp) { 53 | e_printf("Could not open file: %s\n", filename); 54 | return Image(); 55 | } 56 | unsigned int width=0,height=0; 57 | unsigned int maxval=0,nbchans=0; 58 | do { 59 | /* # comments before the first line */ 60 | t = fgets(buf, PPMREADBUFLEN, fp); 61 | if ( t == NULL ) { fclose(fp); return Image(); } 62 | } while ( strncmp(buf, "#", 1) == 0 || strncmp(buf, "\n", 1) == 0); 63 | int type=0; 64 | if ( (!strncmp(buf, "P4", 2)) ) type=4; 65 | if ( (!strncmp(buf, "P5", 2)) ) type=5; 66 | if ( (!strncmp(buf, "P6", 2)) ) type=6; 67 | if ( (!strncmp(buf, "P7", 2)) ) type=7; 68 | if (type==0) { 69 | if (buf[0] == 'P') e_printf("PNM file is not of type P4, P5, P6 or P7, cannot read other types.\n"); 70 | else v_printf(8,"This does not look like a PNM file.\n"); 71 | fclose(fp); 72 | return Image(); 73 | } 74 | t += 2; 75 | 76 | v_printf(7,"Trying to load a PNM/PAM file of type P%i\n",type); 77 | if (type < 7) { 78 | if (!(width = read_pnm_int(fp,buf,&t))) return Image(); 79 | if (!(height = read_pnm_int(fp,buf,&t))) return Image(); 80 | if (type>4) { 81 | if (!(maxval = read_pnm_int(fp,buf,&t))) return Image(); 82 | if ( maxval > 0xffff ) { 83 | e_printf("Invalid PNM file (more than 16-bit?)\n"); 84 | fclose(fp); 85 | return Image(); 86 | } 87 | } else maxval=1; 88 | nbchans=(type==6?3:1); 89 | } else { 90 | int maxlines = 100; 91 | do { 92 | t = fgets(buf, PPMREADBUFLEN, fp); 93 | if ( t == NULL ) { 94 | fclose(fp); 95 | return Image(); 96 | } 97 | if (strncmp(buf, "#", 1) == 0 || strncmp(buf, "\n", 1) == 0) 98 | continue; 99 | 100 | sscanf(buf, "WIDTH %u\n", &width); 101 | sscanf(buf, "HEIGHT %u\n", &height); 102 | sscanf(buf, "DEPTH %u\n", &nbchans); 103 | sscanf(buf, "MAXVAL %u\n", &maxval); 104 | 105 | if (maxlines-- < 1) { 106 | e_printf("Problem while parsing PAM header.\n"); 107 | fclose(fp); 108 | return Image(); 109 | } 110 | } while ( strncmp(buf, "ENDHDR", 6) != 0 ); 111 | if (nbchans>4 || nbchans <1 || width <1 || height < 1 || maxval<1 || maxval > 0xffff) { 112 | e_printf("Couldn't parse PAM header, or unsupported kind of PAM file.\n"); 113 | fclose(fp); 114 | return Image(); 115 | } 116 | } 117 | 118 | v_printf(7,"Loading %ix%i PNM/PAM file with %i channels and maxval=%i\n",width,height,nbchans,maxval); 119 | Image image(width,height, maxval, nbchans); 120 | if (type==4) { 121 | char byte=0; 122 | for (unsigned int y=0; y>(x%8)) ? 0 : 1); 126 | } 127 | } 128 | } else { 129 | if (maxval > 0xff) { 130 | for (unsigned int y=0; y (int)maxval) { 136 | fclose(fp); 137 | e_printf("Invalid PNM/PAM file: value %i is larger than declared maxval %u\n", pixel, maxval); 138 | return Image(); 139 | } 140 | image.channel[c].value(y,x) = pixel; 141 | } 142 | } 143 | } 144 | } else { 145 | for (unsigned int y=0; y= 10504 87 | png_set_scale_16(png_ptr); 88 | #else 89 | png_set_strip_16(png_ptr); 90 | #endif 91 | */ 92 | if (bit_depth < 8) 93 | png_set_packing(png_ptr); 94 | // if (color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) 95 | // png_set_gray_to_rgb(png_ptr); 96 | png_read_update_info(png_ptr, info_ptr); 97 | color_type = png_get_color_type(png_ptr,info_ptr); 98 | 99 | 100 | if (color_type == PNG_COLOR_TYPE_GRAY) nbplanes=1; 101 | else if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA) nbplanes=2; 102 | else if (color_type == PNG_COLOR_TYPE_RGB) nbplanes=3; 103 | else if (color_type == PNG_COLOR_TYPE_RGB_ALPHA) nbplanes=4; 104 | else { png_destroy_read_struct(&png_ptr,(png_infopp) NULL,(png_infopp) NULL); fclose(fp); return Image(); } 105 | 106 | int w = width, h = height, maxval = (bit_depth > 8 ? 65535 : 255); 107 | 108 | int bytes_per_pixel = (bit_depth > 8 ? 2 : 1); 109 | Image image(w,h,maxval,nbplanes); 110 | 111 | std::vector one_row(w*nbplanes*bytes_per_pixel,0); 112 | png_bytep rowptr = &(one_row[0]); 113 | if (bit_depth <= 8) { 114 | int y=0; 115 | while (y < h) { 116 | png_read_row(png_ptr, rowptr, NULL); 117 | for(int x=0; x < w ; x++) { 118 | for (int k=0; k 8) { image.channel[0].value(y,x) += (fgetc(fp)<<8); } 41 | } 42 | } 43 | 44 | for (int c=1; c<3; c++) { 45 | image.channel[c].w = (w+1)/2; 46 | image.channel[c].h = (h+1)/2; 47 | image.channel[c].hshift = 1; 48 | image.channel[c].vshift = 1; 49 | image.channel[c].resize(); 50 | for (int y=0; y<(h+1)/2; y++) { 51 | for (int x=0; x<(w+1)/2; x++) { 52 | image.channel[c].value(y,x) = fgetc(fp); 53 | if (bitdepth > 8) { image.channel[c].value(y,x) += (fgetc(fp)<<8); } 54 | } 55 | } 56 | } 57 | image.transform.push_back(Transform(TRANSFORM_YCbCr)); 58 | Transform subsampling(TRANSFORM_ChromaSubsample); 59 | subsampling.parameters.push_back(0); // 4:2:0 60 | image.transform.push_back(subsampling); 61 | if (fp != stdin) fclose(fp); 62 | return image; 63 | } 64 | -------------------------------------------------------------------------------- /io.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #include 27 | #include 28 | #ifdef _WIN32 29 | #include 30 | #else 31 | #include 32 | #endif 33 | 34 | #include "io.h" 35 | 36 | void e_printf(const char *format, ...) { 37 | va_list args; 38 | va_start(args, format); 39 | vfprintf(stderr, format, args); 40 | fflush(stderr); 41 | va_end(args); 42 | } 43 | 44 | #ifdef DEBUG 45 | static int verbosity = 5; 46 | #else 47 | static int verbosity = 1; 48 | #endif 49 | static FILE * my_stdout = stdout; 50 | void increase_verbosity(int how_much) { 51 | verbosity += how_much; 52 | } 53 | 54 | int get_verbosity() { 55 | return verbosity; 56 | } 57 | 58 | void v_printf(const int v, const char *format, ...) { 59 | if (verbosity < v) return; 60 | va_list args; 61 | va_start(args, format); 62 | vfprintf(my_stdout, format, args); 63 | fflush(my_stdout); 64 | va_end(args); 65 | } 66 | 67 | void v_printf_tty(const int v, const char *format, ...) { 68 | if (verbosity < v) return; 69 | #ifdef _WIN32 70 | if(!_isatty(_fileno(my_stdout))) return; 71 | #else 72 | if(!isatty(fileno(my_stdout))) return; 73 | #endif 74 | va_list args; 75 | va_start(args, format); 76 | vfprintf(my_stdout, format, args); 77 | fflush(my_stdout); 78 | va_end(args); 79 | } 80 | 81 | void redirect_stdout_to_stderr() { 82 | my_stdout = stderr; 83 | } 84 | -------------------------------------------------------------------------------- /io.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | void e_printf(const char *format, ...); 29 | void v_printf(const int v, const char *format, ...); 30 | void v_printf_tty(const int v, const char *format, ...); 31 | void redirect_stdout_to_stderr(); 32 | 33 | void increase_verbosity(int how_much=1); 34 | int get_verbosity(); 35 | 36 | template 37 | bool ioget_int_8bit (IO& io, int* result) 38 | { 39 | int c = io.get_c(); 40 | if (c == io.EOS) { 41 | e_printf ("Unexpected EOS"); 42 | return false; 43 | } 44 | 45 | *result = c; 46 | return true; 47 | } 48 | 49 | template 50 | bool ioget_int_16bit_bigendian (IO& io, int* result) 51 | { 52 | int c1; 53 | int c2; 54 | if (!(ioget_int_8bit (io, &c1) && 55 | ioget_int_8bit (io, &c2))) 56 | return false; 57 | 58 | *result = (c1 << 8) + c2; 59 | return true; 60 | } 61 | -------------------------------------------------------------------------------- /maniac/bit.cpp: -------------------------------------------------------------------------------- 1 | #ifdef _MSC_VER 2 | 3 | #include 4 | int __builtin_clz(unsigned int value) 5 | { 6 | unsigned long r; 7 | _BitScanReverse(&r, value); 8 | return (31 - r); 9 | } 10 | 11 | #endif 12 | -------------------------------------------------------------------------------- /maniac/bit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #ifdef _MSC_VER 3 | 4 | int __builtin_clz(unsigned int value); 5 | 6 | #endif -------------------------------------------------------------------------------- /maniac/chance.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #include "chance.h" 27 | #include "bit.h" 28 | #include 29 | 30 | 31 | void build_table(uint16_t newchances[4096][2], uint32_t factor, unsigned int max_p) { 32 | const int64_t one = 1LL << 32; 33 | const int size = 4096; 34 | int64_t p; 35 | unsigned int last_p8, p8; 36 | unsigned int i; 37 | 38 | memset(newchances,0,sizeof(uint16_t) * size * 2); 39 | 40 | last_p8 = 0; 41 | p = one / 2; 42 | for (i = 0; i < size / 2; i++) { 43 | p8 = (size * p + one / 2) >> 32; //FIXME try without the one 44 | if (p8 <= last_p8) p8 = last_p8 + 1; 45 | if (last_p8 && last_p8 < size && p8 <= max_p) newchances[last_p8][1] = p8; 46 | 47 | p += ((one - p) * factor + one / 2) >> 32; 48 | last_p8 = p8; 49 | } 50 | 51 | for (i = size - max_p; i <= max_p; i++) { 52 | if (newchances[i][1]) continue; 53 | 54 | p = (i * one + size / 2) / size; 55 | p += ((one - p) * factor + one / 2) >> 32; 56 | p8 = (size * p + one / 2) >> 32; //FIXME try without the one 57 | if (p8 <= i) p8 = i + 1; 58 | if (p8 > max_p) p8 = max_p; 59 | newchances[i][1] = p8; 60 | } 61 | 62 | for (i = 1; i < size; i++) 63 | newchances[i][0] = size - newchances[size - i][1]; 64 | 65 | } 66 | 67 | /** Computes an approximation of log(4096 / x) / log(2) * base */ 68 | static uint32_t log4kf(int x, uint32_t base) { 69 | int bits = 8 * sizeof(int) - __builtin_clz(x); 70 | uint64_t y = ((uint64_t)x) << (32 - bits); 71 | uint32_t res = base * (13 - bits); 72 | uint32_t add = base; 73 | while ((add > 1) && ((y & 0x7FFFFFFF) != 0)) { 74 | y = (((uint64_t)y) * y + 0x40000000) >> 31; 75 | add >>= 1; 76 | if ((y >> 32) != 0) { 77 | res -= add; 78 | y >>= 1; 79 | } 80 | } 81 | return res; 82 | } 83 | 84 | Log4kTable::Log4kTable() { 85 | data[0] = 0; 86 | for (int i = 1; i <= 4096; i++) { 87 | data[i] = (log4kf(i, (65535UL << 16) / 12) + (1 << 15)) >> 16; 88 | } 89 | } 90 | 91 | const Log4kTable log4k; 92 | -------------------------------------------------------------------------------- /maniac/chance.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | #include 29 | #include 30 | #include 31 | 32 | 33 | struct Log4kTable { 34 | uint16_t data[4097]; 35 | Log4kTable(); 36 | }; 37 | 38 | extern const Log4kTable log4k; 39 | 40 | void extern build_table(uint16_t newchances[4096][2], uint32_t factor, unsigned int max_p); 41 | 42 | class SimpleBitChanceTable 43 | { 44 | public: 45 | uint16_t newchance[4096][2]; // stored as 12-bit numbers 46 | uint32_t alpha; 47 | 48 | void init(int cut, int alpha_) { 49 | alpha = alpha_; 50 | build_table(newchance, alpha_, 4096-cut); 51 | } 52 | 53 | SimpleBitChanceTable(int cut = 2, int alpha = 0xFFFFFFFF / 19) { 54 | init(cut, alpha); 55 | } 56 | }; 57 | 58 | 59 | class SimpleBitChance 60 | { 61 | protected: 62 | uint16_t chance; // stored as a 12-bit number 63 | 64 | public: 65 | typedef SimpleBitChanceTable Table; 66 | 67 | SimpleBitChance() { 68 | chance = 0x800; 69 | } 70 | 71 | uint16_t inline get_12bit() const { 72 | return chance; 73 | } 74 | void set_12bit(uint16_t chance) { 75 | this->chance = chance; 76 | } 77 | void inline put(bool bit, const Table &table) { 78 | chance = table.newchance[chance][bit]; 79 | } 80 | 81 | void estim(bool bit, uint64_t &total) const { 82 | total += log4k.data[bit ? chance : 4096-chance]; 83 | } 84 | }; -------------------------------------------------------------------------------- /maniac/compound.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | #include 29 | #include 30 | #include 31 | #include "symbol.h" 32 | 33 | 34 | 35 | 36 | typedef int32_t PropertyVal; 37 | typedef std::vector > Ranges; 38 | typedef std::vector Properties; 39 | 40 | // inner nodes 41 | class PropertyDecisionNode { 42 | public: 43 | int16_t property; // -1 : leaf node, childID points to leaf node 44 | // 0..nb_properties-1 : childID refers to left branch (in inner_node) 45 | // childID+1 refers to right branch 46 | uint16_t childID; 47 | PropertyVal splitval; 48 | 49 | // PropertyDecisionNode(int p=-1, int s=0, int c=0) : property(p), count(0), splitval(s), childID(c), leafID(0) {} 50 | PropertyDecisionNode(int p=-1, int s=0, int c=0) : property(p), splitval(s), childID(c) {} 51 | }; 52 | 53 | class Tree : public std::vector { 54 | public: 55 | Tree() : std::vector(1, PropertyDecisionNode()) {} 56 | }; 57 | 58 | typedef std::vector Trees; 59 | 60 | 61 | // leaf nodes when tree is known 62 | template class FinalCompoundSymbolChances { 63 | public: 64 | SymbolChance realChances; 65 | 66 | // FinalCompoundSymbolChances() { } 67 | FinalCompoundSymbolChances(uint16_t zero_chance) : realChances(zero_chance) { } 68 | 69 | const SymbolChance &chances() const { return realChances; } 70 | }; 71 | 72 | 73 | template class FinalCompoundSymbolBitCoder { 74 | public: 75 | typedef typename BitChance::Table Table; 76 | 77 | private: 78 | const Table &table; 79 | RAC &rac; 80 | FinalCompoundSymbolChances &chances; 81 | 82 | void inline updateChances(const SymbolChanceBitType type, const int i, bool bit) { 83 | BitChance& real = chances.realChances.bit(type,i); 84 | real.put(bit, table); 85 | } 86 | 87 | public: 88 | FinalCompoundSymbolBitCoder(const Table &tableIn, RAC &racIn, FinalCompoundSymbolChances &chancesIn) : table(tableIn), rac(racIn), chances(chancesIn) {} 89 | 90 | bool inline read(const SymbolChanceBitType type, const int i = 0) { 91 | BitChance& ch = chances.realChances.bit(type, i); 92 | bool bit = rac.read_12bit_chance(ch.get_12bit()); 93 | updateChances(type, i, bit); 94 | return bit; 95 | } 96 | 97 | #ifdef HAS_ENCODER 98 | void inline write(const bool bit, const SymbolChanceBitType type, const int i = 0); 99 | void estimate(const bool bit, const SymbolChanceBitType type, const int i, uint64_t &total); 100 | #endif 101 | }; 102 | 103 | 104 | 105 | template class FinalCompoundSymbolCoder { 106 | private: 107 | typedef typename FinalCompoundSymbolBitCoder::Table Table; 108 | RAC &rac; 109 | const Table table; 110 | 111 | public: 112 | 113 | FinalCompoundSymbolCoder(RAC& racIn, int cut = 2, int alpha = 0xFFFFFFFF / 19) : rac(racIn), table(cut,alpha) {} 114 | 115 | int read_int(FinalCompoundSymbolChances &chancesIn, int min, int max) { 116 | FinalCompoundSymbolBitCoder bitCoder(table, rac, chancesIn); 117 | int val = reader(bitCoder, min, max); 118 | return val; 119 | } 120 | int read_int(FinalCompoundSymbolChances &chancesIn, int nbits) { 121 | FinalCompoundSymbolBitCoder bitCoder(table, rac, chancesIn); 122 | int val = reader(bitCoder, nbits); 123 | return val; 124 | } 125 | 126 | #ifdef HAS_ENCODER 127 | void write_int(FinalCompoundSymbolChances& chancesIn, int min, int max, int val); 128 | int estimate_int(FinalCompoundSymbolChances& chancesIn, int min, int max, int val); 129 | void write_int(FinalCompoundSymbolChances& chancesIn, int nbits, int val); 130 | #endif 131 | }; 132 | 133 | 134 | 135 | template class FinalPropertySymbolCoder { 136 | private: 137 | FinalCompoundSymbolCoder coder; 138 | unsigned int nb_properties; 139 | std::vector > leaf_node; 140 | Tree &inner_node; 141 | 142 | FinalCompoundSymbolChances inline &find_leaf(const Properties &properties) ATTRIBUTE_HOT { 143 | Tree::size_type pos = 0; 144 | 145 | while(true) { 146 | // if (inner_node[pos].property == -1) assert(inner_node[pos].leafID < leaf_node.size()); 147 | // if (inner_node[pos].property == -1) return leaf_node[inner_node[pos].leafID]; 148 | // if (inner_node[pos].count >= 0) break; 149 | if (inner_node[pos].property == -1) return leaf_node[inner_node[pos].childID]; 150 | if (properties[inner_node[pos].property] > inner_node[pos].splitval) 151 | pos = inner_node[pos].childID; 152 | else pos = inner_node[pos].childID+1; 153 | } 154 | /* 155 | if (inner_node[pos].count > 0) { 156 | inner_node[pos].count--; 157 | return leaf_node[inner_node[pos].leafID]; 158 | } else if (inner_node[pos].count == 0) { 159 | inner_node[pos].count--; 160 | FinalCompoundSymbolChances &result = leaf_node[inner_node[pos].leafID]; 161 | uint32_t old_leaf = inner_node[pos].leafID; 162 | uint32_t new_leaf = leaf_node.size(); 163 | leaf_node.push_back(result); 164 | inner_node[inner_node[pos].childID].leafID = old_leaf; 165 | inner_node[inner_node[pos].childID+1].leafID = new_leaf; 166 | if (properties[inner_node[pos].property] > inner_node[pos].splitval) { 167 | return leaf_node[old_leaf]; 168 | } else { 169 | return leaf_node[new_leaf]; 170 | } 171 | } 172 | */ 173 | /* 174 | 175 | while(inner_node[pos].property != -1) { 176 | if (inner_node[pos].count < 0) { 177 | if (properties[inner_node[pos].property] > inner_node[pos].splitval) { 178 | pos = inner_node[pos].childID; 179 | } else { 180 | pos = inner_node[pos].childID+1; 181 | } 182 | } else if (inner_node[pos].count > 0) { 183 | assert(inner_node[pos].leafID >= 0); 184 | assert((unsigned int)inner_node[pos].leafID < leaf_node.size()); 185 | inner_node[pos].count--; 186 | break; 187 | } else if (inner_node[pos].count == 0) { 188 | inner_node[pos].count--; 189 | FinalCompoundSymbolChances &result = leaf_node[inner_node[pos].leafID]; 190 | uint32_t old_leaf = inner_node[pos].leafID; 191 | uint32_t new_leaf = leaf_node.size(); 192 | FinalCompoundSymbolChances resultCopy = result; 193 | leaf_node.push_back(resultCopy); 194 | inner_node[inner_node[pos].childID].leafID = old_leaf; 195 | inner_node[inner_node[pos].childID+1].leafID = new_leaf; 196 | if (properties[inner_node[pos].property] > inner_node[pos].splitval) { 197 | return leaf_node[old_leaf]; 198 | } else { 199 | return leaf_node[new_leaf]; 200 | } 201 | } 202 | } 203 | return leaf_node[inner_node[pos].leafID]; 204 | */ 205 | } 206 | 207 | #ifdef HAS_ENCODER 208 | FinalCompoundSymbolChances inline &find_leaf_readonly(const Properties &properties); 209 | #endif 210 | 211 | 212 | public: 213 | FinalPropertySymbolCoder(RAC& racIn, Ranges &rangeIn, Tree &treeIn, uint16_t zero_chance=ZERO_CHANCE, int ignored_split_threshold = 0, int cut = 4, int alpha = 0xFFFFFFFF / 20) : 214 | coder(racIn, cut, alpha), 215 | nb_properties(rangeIn.size()), 216 | // leaf_node(1,FinalCompoundSymbolChances(zero_chance)), 217 | leaf_node((treeIn.size()+1)/2,FinalCompoundSymbolChances(zero_chance)), 218 | inner_node(treeIn) 219 | { 220 | // inner_node[0].leafID = 0; 221 | 222 | for (int i=0, leafID=0; i &chances = find_leaf(properties); 231 | return coder.read_int(chances, min, max); 232 | } 233 | 234 | 235 | int read_int(const Properties &properties, int nbits) { 236 | assert(properties.size() == nb_properties); 237 | FinalCompoundSymbolChances &chances = find_leaf(properties); 238 | return coder.read_int(chances, nbits); 239 | } 240 | 241 | #ifdef HAS_ENCODER 242 | void write_int(const Properties &properties, int min, int max, int val); 243 | int estimate_int(const Properties &properties, int min, int max, int val); 244 | void write_int(const Properties &properties, int nbits, int val); 245 | static void simplify(int divisor=CONTEXT_TREE_COUNT_DIV, int min_size=CONTEXT_TREE_MIN_SUBTREE_SIZE, int plane=0) {} 246 | static uint64_t compute_total_size() {return 0;} 247 | #endif 248 | 249 | }; 250 | 251 | 252 | 253 | template class MetaPropertySymbolCoder { 254 | public: 255 | typedef SimpleSymbolCoder Coder; 256 | private: 257 | std::vector coder; 258 | const Ranges range; 259 | unsigned int nb_properties; 260 | 261 | public: 262 | MetaPropertySymbolCoder(RAC &racIn, const Ranges &rangesIn, int cut = 2, int alpha = 0xFFFFFFFF / 19) : 263 | coder(3,Coder(racIn, cut, alpha)), 264 | range(rangesIn), 265 | nb_properties(rangesIn.size()) 266 | { 267 | for (unsigned int i=0; imaxdepth) maxdepth=depth; 282 | if (p != -1) { 283 | int oldmin = subrange[p].first; 284 | int oldmax = subrange[p].second; 285 | if (oldmin >= oldmax) { 286 | e_printf( "Invalid tree. Aborting tree decoding.\n"); 287 | return false; 288 | } 289 | // n.count = coder[1].read_int2(CONTEXT_TREE_MIN_COUNT, CONTEXT_TREE_MAX_COUNT); 290 | assert(oldmin < oldmax); 291 | int splitval = n.splitval = coder[2].read_int2(oldmin, oldmax-1); 292 | int childID = n.childID = tree.size(); 293 | // e_printf( "Pos %i: prop %i splitval %i in [%i..%i]\n", pos, n.property, splitval, oldmin, oldmax-1); 294 | tree.push_back(PropertyDecisionNode()); 295 | tree.push_back(PropertyDecisionNode()); 296 | // > splitval 297 | subrange[p].first = splitval+1; 298 | if (!read_subtree(childID, subrange, tree, maxdepth, depth)) return false; 299 | 300 | // <= splitval 301 | subrange[p].first = oldmin; 302 | subrange[p].second = splitval; 303 | if (!read_subtree(childID+1, subrange, tree, maxdepth, depth)) return false; 304 | 305 | subrange[p].second = oldmax; 306 | } 307 | return true; 308 | } 309 | bool read_tree(Tree &tree) { 310 | Ranges rootrange(range); 311 | tree.clear(); 312 | tree.push_back(PropertyDecisionNode()); 313 | int depth=0; 314 | if (read_subtree(0, rootrange, tree, depth, 0)) { 315 | int neededdepth = maniac::util::ilog2(tree.size())+1; 316 | v_printf(8,"Read MANIAC tree with %u nodes and depth %i (with better balance, depth %i might have been enough).\n",(unsigned int) tree.size(),depth,neededdepth); 317 | return true; 318 | } else return false; 319 | //return read_subtree(0, rootrange, tree); 320 | } 321 | }; 322 | 323 | #ifdef HAS_ENCODER 324 | #include "compound_enc.h" 325 | #endif 326 | -------------------------------------------------------------------------------- /maniac/rac.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | 29 | #include 30 | #include 31 | #include "../config.h" 32 | 33 | 34 | /* RAC configuration for 24-bit RAC */ 35 | class RacConfig24 { 36 | public: 37 | typedef uint_fast32_t data_t; 38 | static const data_t MAX_RANGE_BITS = 24; 39 | static const data_t MIN_RANGE_BITS = 16; 40 | static const data_t MIN_RANGE = (1UL << MIN_RANGE_BITS); 41 | static const data_t BASE_RANGE = (1UL << MAX_RANGE_BITS); 42 | 43 | static inline data_t chance_12bit_chance(int b12, data_t range) ATTRIBUTE_HOT { 44 | // assert(b12 > 0); 45 | assert((b12 >> 12) == 0); 46 | // We want to compute (range * b12 + 0x800) >> 12. On 64-bit architectures this is no problem 47 | if (sizeof(data_t) > 4) return (range * b12 + 0x800) >> 12; 48 | // Unfortunately, this can overflow the 32-bit data type on 32-bit architectures, so split range 49 | // in its lower and upper 12 bits, and compute separately. 50 | else return ((((range & 0xFFF) * b12 + 0x800) >> 12) + ((range >> 12) * b12)); 51 | // (no worries, the compiler eliminates this branching) 52 | } 53 | }; 54 | 55 | template class RacInput { 56 | public: 57 | typedef typename Config::data_t rac_t; 58 | protected: 59 | IO& io; 60 | private: 61 | rac_t range; 62 | rac_t low; 63 | private: 64 | rac_t read_catch_eof() { 65 | rac_t c = io.get_c(); 66 | // no reason to branch here to catch end-of-stream, just return garbage (0xFF I guess) if a premature EOS happens 67 | //if(c == io.EOS) return 0; 68 | return c; 69 | } 70 | void inline input() { 71 | if (range <= Config::MIN_RANGE) { 72 | low <<= 8; 73 | range <<= 8; 74 | low |= read_catch_eof(); 75 | } 76 | if (range <= Config::MIN_RANGE) { 77 | low <<= 8; 78 | range <<= 8; 79 | low |= read_catch_eof(); 80 | } 81 | } 82 | bool inline get(rac_t chance) { 83 | assert(chance >= 0); 84 | assert(chance < range); 85 | if (low >= range-chance) { 86 | low -= range-chance; 87 | range = chance; 88 | input(); 89 | return true; 90 | } else { 91 | range -= chance; 92 | input(); 93 | return false; 94 | } 95 | } 96 | public: 97 | explicit RacInput(IO& ioin) : io(ioin), range(Config::BASE_RANGE), low(0) { 98 | rac_t r = Config::BASE_RANGE; 99 | while (r > 1) { 100 | low <<= 8; 101 | low |= read_catch_eof(); 102 | r >>= 8; 103 | } 104 | } 105 | 106 | 107 | bool inline read_12bit_chance(uint16_t b12) ATTRIBUTE_HOT { 108 | return get(Config::chance_12bit_chance(b12, range)); 109 | } 110 | 111 | bool inline read_bit() { 112 | return get(range >> 1); 113 | } 114 | }; 115 | 116 | 117 | template class RacInput24 : public RacInput { 118 | public: 119 | explicit RacInput24(IO& io) : RacInput(io) { } 120 | }; 121 | 122 | #ifdef HAS_ENCODER 123 | #include "rac_enc.h" 124 | #endif 125 | -------------------------------------------------------------------------------- /maniac/rac_enc.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | template class RacOutput { 29 | public: 30 | typedef typename Config::data_t rac_t; 31 | protected: 32 | IO& io; 33 | private: 34 | rac_t range; 35 | rac_t low; 36 | int delayed_byte; 37 | int delayed_count; 38 | 39 | void inline output() { 40 | while (range <= Config::MIN_RANGE) { 41 | int byte = low >> Config::MIN_RANGE_BITS; 42 | if (delayed_byte < 0) { // first generated byte 43 | delayed_byte = byte; 44 | } else if (((low + range) >> 8) < Config::MIN_RANGE) { // definitely no overflow 45 | io.fputc(delayed_byte); 46 | while (delayed_count) { 47 | io.fputc(0xFF); 48 | delayed_count--; 49 | } 50 | delayed_byte = byte; 51 | } else if ((low >> 8) >= Config::MIN_RANGE) { // definitely overflow 52 | io.fputc(delayed_byte + 1); 53 | while (delayed_count) { 54 | io.fputc(0); 55 | delayed_count--; 56 | } 57 | delayed_byte = byte & 0xFF; 58 | } else { 59 | delayed_count++; 60 | } 61 | low = (low & (Config::MIN_RANGE - 1)) << 8; 62 | range <<= 8; 63 | } 64 | } 65 | void inline put(rac_t chance, bool bit) { 66 | assert(chance >= 0); 67 | assert(chance < range); 68 | if (bit) { 69 | low += range - chance; 70 | range = chance; 71 | } else { 72 | range -= chance; 73 | } 74 | output(); 75 | } 76 | public: 77 | RacOutput(IO& ioin) : io(ioin), range(Config::BASE_RANGE), low(0), delayed_byte(-1), delayed_count(0) { } 78 | 79 | void inline write_12bit_chance(uint16_t b12, bool bit) { 80 | put(Config::chance_12bit_chance(b12, range), bit); 81 | } 82 | 83 | void inline write_bit(bool bit) { 84 | put(range >> 1, bit); 85 | } 86 | 87 | void inline flush() { 88 | low += (Config::MIN_RANGE - 1); 89 | // is this the correct way to reliably flush? 90 | range = Config::MIN_RANGE - 1; 91 | output(); 92 | range = Config::MIN_RANGE - 1; 93 | output(); 94 | range = Config::MIN_RANGE - 1; 95 | output(); 96 | range = Config::MIN_RANGE - 1; 97 | output(); 98 | io.flush(); 99 | } 100 | }; 101 | 102 | 103 | template 104 | class RacDummy { 105 | public: 106 | static void inline write_12bit_chance(uint16_t b12, bool) { } 107 | static void inline write_bit(bool) { } 108 | static void inline flush() { } 109 | RacDummy(IO &io) {} 110 | }; 111 | 112 | 113 | template class RacOutput24 : public RacOutput { 114 | public: 115 | RacOutput24(IO& io) : RacOutput(io) { } 116 | }; 117 | -------------------------------------------------------------------------------- /maniac/symbol.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | #include 29 | #include 30 | #include "util.h" 31 | #include "chance.h" 32 | 33 | template class UniformSymbolCoder { 34 | private: 35 | RAC &rac; 36 | 37 | public: 38 | explicit UniformSymbolCoder(RAC &racIn) : rac(racIn) { } 39 | 40 | #ifdef HAS_ENCODER 41 | void write_int(int min, int max, int val); 42 | void write_int(int bits, int val) { write_int(0, (1<= 0); 46 | if (len == 0) return min; 47 | 48 | // split in [0..med] [med+1..len] 49 | int med = len/2; 50 | bool bit = rac.read_bit(); 51 | if (bit) { 52 | return read_int(min+med+1, len-(med+1)); 53 | } else { 54 | return read_int(min, med); 55 | } 56 | } 57 | int read_int(int bits) { return read_int(0, (1< class SymbolChance { 73 | public: 74 | BitChance bit_zero; 75 | BitChance bit_sign; 76 | BitChance bit_exp[bits-1]; 77 | BitChance bit_mant[bits]; 78 | 79 | public: 80 | 81 | BitChance inline &bitZero() { 82 | return bit_zero; 83 | } 84 | 85 | BitChance inline &bitSign() { 86 | return bit_sign; 87 | } 88 | 89 | // all exp bits 0 -> int(log2(val)) == 0 [ val == 1 ] 90 | // exp bits up to i are 1 -> int(log2(val)) == i+1 91 | BitChance inline &bitExp(int i) { 92 | assert(i >= 0 && i < (bits-1)); 93 | return bit_exp[i]; 94 | } 95 | BitChance inline &bitMant(int i) { 96 | assert(i >= 0 && i < bits); 97 | return bit_mant[i]; 98 | } 99 | 100 | BitChance inline &bit(SymbolChanceBitType typ, int i = 0) { 101 | switch (typ) { 102 | default: 103 | case BIT_ZERO: 104 | return bitZero(); 105 | case BIT_SIGN: 106 | return bitSign(); 107 | case BIT_EXP: 108 | return bitExp(i); 109 | case BIT_MANT: 110 | return bitMant(i); 111 | } 112 | } 113 | SymbolChance() { } // don't init (needed for fast copy constructor in CompoundSymbolChances?) 114 | 115 | SymbolChance(uint16_t zero_chance) { 116 | bitZero().set_12bit(zero_chance); 117 | // bitSign().set_12bit(0x800); // 50%, which is the default anyway 118 | uint64_t p = zero_chance; // implicit denominator of 0x1000 119 | uint64_t rp = 0x1000 - zero_chance; // 1-p 120 | 121 | // assume geometric distribution: Pr(X=k) = (1-p)^k p 122 | // (p = zero_chance) 123 | // cumulative distribution function: Pr(X < k) = 1-(1-p)^k = 1-rp^k 124 | // Pr(X >= k) = (1-p)^k = rp^k 125 | // bitExp(i) == true iff X >= 2^i && X < 2^(i+1) 126 | // bitExp(i) is only used if the lower bound holds 127 | // Pr(X >= 2^i && X < 2^(i+1) | X >= 2^i ) = (rp^2^i - rp^2^(i+1)) / rp^2^i = 1 - rp^2^i 128 | 129 | for (int i=0; i 0xf00) rp = 0xf00; 132 | bitExp(i).set_12bit(0x1000 - rp); 133 | rp = (rp*rp + 0x800) >> 12; // square it 134 | } 135 | for (int i=0; i int reader(SymbolCoder& coder, int bits) { 142 | int pos=0; 143 | int value=0; 144 | int b=1; 145 | while (pos++ < bits) { 146 | if (coder.read(BIT_MANT, pos)) value += b; 147 | b *= 2; 148 | } 149 | return value; 150 | } 151 | 152 | template int reader(SymbolCoder& coder, int min, int max) ATTRIBUTE_HOT; 153 | 154 | template int reader(SymbolCoder& coder, int min, int max) { 155 | assert(min<=max); 156 | if (min == max) return min; 157 | 158 | bool sign; 159 | assert(min <= 0 && max >= 0); // should always be the case, because guess should always be in valid range 160 | 161 | if (coder.read(BIT_ZERO)) return 0; 162 | if (min < 0) { 163 | if (max > 0) { 164 | sign = coder.read(BIT_SIGN); 165 | } else {sign = false; } 166 | } else {sign = true; } 167 | 168 | const int amax = (sign? max : -min); 169 | const int emax = maniac::util::ilog2(amax); 170 | 171 | int e = 0; 172 | for (; e < emax; e++) { 173 | if (coder.read(BIT_EXP,e)) break; 174 | } 175 | 176 | int have = (1 << e); 177 | 178 | for (int pos = e; pos>0;) { 179 | pos--; 180 | int minabs1 = have | (1< amax) continue; // 1-bit is impossible 182 | if (coder.read(BIT_MANT,pos)) have = minabs1; 183 | } 184 | return (sign ? have : -have); 185 | } 186 | 187 | template class SimpleSymbolBitCoder { 188 | typedef typename BitChance::Table Table; 189 | 190 | private: 191 | const Table &table; 192 | SymbolChance &ctx; 193 | RAC &rac; 194 | 195 | public: 196 | SimpleSymbolBitCoder(const Table &tableIn, SymbolChance &ctxIn, RAC &racIn) : table(tableIn), ctx(ctxIn), rac(racIn) {} 197 | 198 | #ifdef HAS_ENCODER 199 | void write(bool bit, SymbolChanceBitType typ, int i = 0); 200 | #endif 201 | 202 | bool read(SymbolChanceBitType typ, int i = 0) { 203 | BitChance& bch = ctx.bit(typ, i); 204 | bool bit = rac.read_12bit_chance(bch.get_12bit()); 205 | bch.put(bit, table); 206 | return bit; 207 | } 208 | }; 209 | 210 | template class SimpleSymbolCoder { 211 | typedef typename BitChance::Table Table; 212 | 213 | private: 214 | SymbolChance ctx; 215 | const Table table; 216 | RAC &rac; 217 | 218 | public: 219 | SimpleSymbolCoder(RAC& racIn, int cut = 2, int alpha = 0xFFFFFFFF / 19) : ctx(ZERO_CHANCE), table(cut,alpha), rac(racIn) { } 220 | 221 | #ifdef HAS_ENCODER 222 | void write_int(int min, int max, int value); 223 | void write_int2(int min, int max, int value) { 224 | if (min>0) write_int(0,max-min,value-min); 225 | else if (max<0) write_int(min-max,0,value-max); 226 | else write_int(min,max,value); 227 | } 228 | void write_int(int nbits, int value); 229 | #endif 230 | 231 | int read_int(int min, int max) { 232 | SimpleSymbolBitCoder bitCoder(table, ctx, rac); 233 | return reader>(bitCoder, min, max); 234 | } 235 | int read_int2(int min, int max) { 236 | if (min > 0) return read_int(0,max-min)+min; 237 | else if (max<0) return read_int(min-max,0)+max; 238 | else return read_int(min,max); 239 | } 240 | int read_int(int nbits) { 241 | assert (nbits <= bits); 242 | SimpleSymbolBitCoder bitCoder(table, ctx, rac); 243 | return reader(bitCoder, nbits); 244 | } 245 | }; 246 | 247 | #ifdef HAS_ENCODER 248 | #include "symbol_enc.h" 249 | #endif 250 | -------------------------------------------------------------------------------- /maniac/symbol_enc.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | template 29 | void UniformSymbolCoder::write_int(int min, int max, int val) { 30 | assert(max >= min); 31 | if (min != 0) { 32 | max -= min; 33 | val -= min; 34 | } 35 | if (max == 0) return; 36 | 37 | // split in [0..med] [med+1..max] 38 | int med = max/2; 39 | if (val > med) { 40 | rac.write_bit(true); 41 | write_int(med+1, max, val); 42 | } else { 43 | rac.write_bit(false); 44 | write_int(0, med, val); 45 | } 46 | return; 47 | } 48 | 49 | 50 | template void writer(SymbolCoder& coder, int bits, int value) { 51 | int pos=0; 52 | while (pos++ < bits) { 53 | coder.write(value&1, BIT_MANT, pos); 54 | value >>= 1; 55 | } 56 | } 57 | 58 | template void writer(SymbolCoder& coder, int min, int max, int value) { 59 | assert(min<=max); 60 | assert(value>=min); 61 | assert(value<=max); 62 | 63 | // avoid doing anything if the value is already known 64 | if (min == max) return; 65 | 66 | if (value == 0) { // value is zero 67 | coder.write(true, BIT_ZERO); 68 | return; 69 | } 70 | 71 | assert(min <= 0 && max >= 0); // should always be the case, because guess should always be in valid range 72 | 73 | coder.write(false,BIT_ZERO); 74 | int sign = (value > 0 ? 1 : 0); 75 | if (max > 0 && min < 0) { 76 | // only output sign bit if value can be both pos and neg 77 | coder.write(sign,BIT_SIGN); 78 | } 79 | const int a = abs(value); 80 | const int e = maniac::util::ilog2(a); 81 | int amax = sign ? abs(max) : abs(min); 82 | 83 | int emax = maniac::util::ilog2(amax); 84 | 85 | int i = 0; //maniac::util::ilog2(amin); 86 | while (i < emax) { 87 | // if exponent >i is impossible, we are done 88 | if ((1 << (i+1)) > amax) break; 89 | // if exponent i is possible, output the exponent bit 90 | coder.write(i==e, BIT_EXP, i); 91 | if (i==e) break; 92 | i++; 93 | } 94 | 95 | int have = (1 << e); 96 | int left = have-1; 97 | for (int pos = e; pos>0;) { 98 | int bit = 1; 99 | left ^= (1 << (--pos)); 100 | int minabs1 = have | (1< amax) { // 1-bit is impossible 102 | bit = 0; 103 | } else { 104 | bit = (a >> pos) & 1; 105 | coder.write(bit, BIT_MANT, pos); 106 | } 107 | have |= (bit << pos); 108 | } 109 | } 110 | 111 | template int estimate_writer(SymbolCoder& coder, int min, int max, int value) { 112 | assert(min<=max); 113 | assert(value>=min); 114 | assert(value<=max); 115 | 116 | uint64_t total=0; 117 | 118 | // avoid doing anything if the value is already known 119 | if (min == max) return total; 120 | 121 | if (value == 0) { // value is zero 122 | coder.estimate(true, BIT_ZERO,0,total); 123 | return total; 124 | } 125 | 126 | assert(min <= 0 && max >= 0); // should always be the case, because guess should always be in valid range 127 | 128 | coder.estimate(false,BIT_ZERO,0,total); 129 | int sign = (value > 0 ? 1 : 0); 130 | if (max > 0 && min < 0) { 131 | // only output sign bit if value can be both pos and neg 132 | coder.estimate(sign,BIT_SIGN,0,total); 133 | } 134 | if (sign) min = 1; 135 | if (!sign) max = -1; 136 | const int a = abs(value); 137 | const int e = maniac::util::ilog2(a); 138 | int amin = sign ? abs(min) : abs(max); 139 | int amax = sign ? abs(max) : abs(min); 140 | 141 | int emax = maniac::util::ilog2(amax); 142 | int i = maniac::util::ilog2(amin); 143 | 144 | while (i < emax) { 145 | // if exponent >i is impossible, we are done 146 | if ((1 << (i+1)) > amax) break; 147 | // if exponent i is possible, output the exponent bit 148 | coder.estimate(i==e, BIT_EXP, i,total); 149 | if (i==e) break; 150 | i++; 151 | } 152 | 153 | int have = (1 << e); 154 | int left = have-1; 155 | for (int pos = e; pos>0;) { 156 | int bit = 1; 157 | left ^= (1 << (--pos)); 158 | int minabs1 = have | (1< amax) { // 1-bit is impossible 160 | bit = 0; 161 | } else { 162 | bit = (a >> pos) & 1; 163 | coder.estimate(bit, BIT_MANT, pos,total); 164 | } 165 | have |= (bit << pos); 166 | } 167 | // printf("Total to write %i in %i..%i: %lu\n",value,min,max,total); 168 | return total; 169 | } 170 | 171 | 172 | template 173 | void SimpleSymbolBitCoder::write(bool bit, SymbolChanceBitType typ, int i) { 174 | BitChance& bch = ctx.bit(typ, i); 175 | rac.write_12bit_chance(bch.get_12bit(), bit); 176 | bch.put(bit, table); 177 | } 178 | 179 | 180 | template 181 | void SimpleSymbolCoder::write_int(int min, int max, int value) { 182 | SimpleSymbolBitCoder bitCoder(table, ctx, rac); 183 | writer >(bitCoder, min, max, value); 184 | } 185 | template 186 | void SimpleSymbolCoder::write_int(int nbits, int value) { 187 | assert (nbits <= bits); 188 | SimpleSymbolBitCoder bitCoder(table, ctx, rac); 189 | writer(bitCoder, nbits, value); 190 | } 191 | -------------------------------------------------------------------------------- /maniac/util.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2010-2016, Jon Sneyers & Pieter Wuille 4 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | 24 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 25 | 26 | #pragma once 27 | 28 | #include 29 | #include "bit.h" 30 | 31 | namespace maniac { 32 | namespace util { 33 | 34 | static inline int ilog2(uint32_t l) { 35 | if (l == 0) { return 0; } 36 | return sizeof(unsigned int) * 8 - 1 - __builtin_clz(l); 37 | } 38 | 39 | void indent(int n); 40 | 41 | 42 | } // namespace util 43 | } // namespace maniac 44 | -------------------------------------------------------------------------------- /transform/approximate.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | 27 | #include "../image/image.h" 28 | #include "../io.h" 29 | 30 | 31 | 32 | bool inv_approximate(Image &input, const std::vector ¶meters) { 33 | int beginc = parameters[0]; 34 | int endc = parameters[1]; 35 | int offset = input.channel.size() - (endc - beginc + 1); 36 | for (int c=beginc; c<=endc; c++) { 37 | int q = (c+2-beginc < parameters.size() ? parameters[c+2-beginc] : parameters.back()); 38 | if (!q) offset++; 39 | } 40 | v_printf(3,"Reconstructing approximated channels %i-%i using remainder channels %i-%i.\n",beginc,endc,offset,input.channel.size()-1); 41 | int i=0; 42 | for (int c=beginc; c<=endc; c++) { 43 | int q = (c+2-beginc < parameters.size() ? parameters[c+2-beginc] : parameters.back()); 44 | q += 1; 45 | if (q == 1) continue; 46 | Channel &ch = input.channel[c]; 47 | const Channel &chr = input.channel[offset+i]; 48 | i++; 49 | if (chr.data.size() == 0) v_printf(3,"Remainder channel is not available.\n"); 50 | else ch.q = chr.q; // usually these are the same, except when approximated channel became all zeroes 51 | for (int y=0; y ¶meters) { 64 | if (parameters.size() < 3) { 65 | e_printf("Incorrect number of parameters for Approximation transform.\n"); 66 | image.error = true; 67 | return; 68 | } 69 | int nb = parameters[1] - parameters[0] + 1; 70 | if (nb < 1 || parameters[0] < 0 || parameters[1] >= image.channel.size()) { 71 | e_printf("Incorrect parameters for Approximation transform.\n"); 72 | image.error = true; 73 | return; 74 | } 75 | for (int c = parameters[0]; c <= parameters[1]; c++) { 76 | int q = (c+2-parameters[0] < parameters.size() ? parameters[c+2-parameters[0]] : parameters.back()); 77 | if (q) image.channel.push_back(image.channel[c]); 78 | } 79 | } 80 | 81 | 82 | bool fwd_approximate(Image &input, std::vector ¶meters) { 83 | int offset = input.channel.size(); 84 | meta_approximate(input,parameters); 85 | int beginc = parameters[0]; 86 | int endc = parameters[1]; 87 | v_printf(3,"Approximating channels %i-%i\n",beginc,endc); 88 | int i=0; 89 | for (int c=beginc; c<=endc; c++) { 90 | int q = (c+2-beginc < parameters.size() ? parameters[c+2-beginc] : parameters.back()); 91 | q += 1; 92 | if (q == 1) continue; 93 | Channel &ch = input.channel[c]; 94 | Channel &chr = input.channel[offset+i]; 95 | i++; 96 | for (int y=0; y ¶meters) { 116 | if (inverse) return inv_approximate(input, parameters); 117 | else return fwd_approximate(input, parameters); 118 | } 119 | 120 | -------------------------------------------------------------------------------- /transform/dct.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | // float DCT, code taken from guetzli 26 | 27 | // This transform corresponds to JPEG's 8x8 DCT applied to all channels (by default) or some channels (given by the optional parameters). 28 | // It replaces each channel by 64 new channels (with dimensions 1/8), one for each DCT coefficient. 29 | // These new channels are ordered using a fixed 'scan script'; if you want a different order, you can use an explicit TRANSFORM_PERMUTE 30 | 31 | 32 | /* 33 | * Copyright 2016 Google Inc. 34 | * 35 | * Licensed under the Apache License, Version 2.0 (the "License"); 36 | * you may not use this file except in compliance with the License. 37 | * You may obtain a copy of the License at 38 | * 39 | * http://www.apache.org/licenses/LICENSE-2.0 40 | * 41 | * Unless required by applicable law or agreed to in writing, software 42 | * distributed under the License is distributed on an "AS IS" BASIS, 43 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 44 | * See the License for the specific language governing permissions and 45 | * limitations under the License. 46 | */ 47 | 48 | #pragma once 49 | 50 | #include "../image/image.h" 51 | 52 | 53 | 54 | #include 55 | #include 56 | 57 | 58 | // kDCTMatrix[8*u+x] = 0.5*alpha(u)*cos((2*x+1)*u*M_PI/16), 59 | // where alpha(0) = 1/sqrt(2) and alpha(u) = 1 for u > 0. 60 | static const double kDCTMatrix[64] = { 61 | 0.3535533906, 0.3535533906, 0.3535533906, 0.3535533906, 62 | 0.3535533906, 0.3535533906, 0.3535533906, 0.3535533906, 63 | 0.4903926402, 0.4157348062, 0.2777851165, 0.0975451610, 64 | -0.0975451610, -0.2777851165, -0.4157348062, -0.4903926402, 65 | 0.4619397663, 0.1913417162, -0.1913417162, -0.4619397663, 66 | -0.4619397663, -0.1913417162, 0.1913417162, 0.4619397663, 67 | 0.4157348062, -0.0975451610, -0.4903926402, -0.2777851165, 68 | 0.2777851165, 0.4903926402, 0.0975451610, -0.4157348062, 69 | 0.3535533906, -0.3535533906, -0.3535533906, 0.3535533906, 70 | 0.3535533906, -0.3535533906, -0.3535533906, 0.3535533906, 71 | 0.2777851165, -0.4903926402, 0.0975451610, 0.4157348062, 72 | -0.4157348062, -0.0975451610, 0.4903926402, -0.2777851165, 73 | 0.1913417162, -0.4619397663, 0.4619397663, -0.1913417162, 74 | -0.1913417162, 0.4619397663, -0.4619397663, 0.1913417162, 75 | 0.0975451610, -0.2777851165, 0.4157348062, -0.4903926402, 76 | 0.4903926402, -0.4157348062, 0.2777851165, -0.0975451610, 77 | }; 78 | 79 | void DCT1d(const double* in, int stride, double* out) { 80 | for (int x = 0; x < 8; ++x) { 81 | out[x * stride] = 0.0; 82 | for (int u = 0; u < 8; ++u) { 83 | out[x * stride] += kDCTMatrix[8 * x + u] * in[u * stride]; 84 | } 85 | } 86 | } 87 | 88 | void IDCT1d(const double* in, int stride, double* out) { 89 | for (int x = 0; x < 8; ++x) { 90 | out[x * stride] = 0.0; 91 | for (int u = 0; u < 8; ++u) { 92 | out[x * stride] += kDCTMatrix[8 * u + x] * in[u * stride]; 93 | } 94 | } 95 | } 96 | 97 | typedef void (*Transform1d)(const double* in, int stride, double* out); 98 | 99 | void TransformBlock(double block[64], Transform1d f) { 100 | double tmp[64]; 101 | for (int x = 0; x < 8; ++x) { 102 | f(&block[x], 8, &tmp[x]); 103 | } 104 | for (int y = 0; y < 8; ++y) { 105 | f(&tmp[8 * y], 1, &block[8 * y]); 106 | } 107 | } 108 | 109 | 110 | void ComputeBlockDCTDouble(double block[64]) { 111 | TransformBlock(block, DCT1d); 112 | } 113 | 114 | void ComputeBlockIDCTDouble(double block[64]) { 115 | TransformBlock(block, IDCT1d); 116 | } 117 | 118 | 119 | // we use a variant 120 | const int jpeg_zigzag[64] = { 121 | 122 | 0, 1, 4, 15, 16, 35, 36, 63, 123 | 2, 3, 5, 14, 17, 34, 37, 62, 124 | 8, 7, 6, 13, 18, 33, 38, 61, 125 | 9, 10, 11, 12, 19, 32, 39, 60, 126 | 24, 23, 22, 21, 20, 31, 40, 59, 127 | 25, 26, 27, 28, 29, 30, 41, 58, 128 | 48, 47, 46, 45, 44, 43, 42, 57, 129 | 49, 50, 51, 52, 53, 54, 55, 56 130 | /* 131 | 0, 1, 5, 6, 14, 15, 27, 28, 132 | 2, 3, 7, 13, 16, 26, 29, 42, 133 | 4, 8, 12, 17, 25, 30, 41, 43, 134 | 9, 11, 18, 24, 31, 40, 44, 53, 135 | 10, 19, 23, 32, 39, 45, 52, 54, 136 | 20, 22, 33, 38, 46, 51, 55, 60, 137 | 21, 34, 37, 47, 50, 56, 59, 61, 138 | 35, 36, 48, 49, 57, 58, 62, 63 139 | */ 140 | }; 141 | 142 | /* 143 | // This is the actual zigzag order of JPEG 144 | const int jpeg_zigzag[64] = { 145 | 0, 1, 5, 6, 14, 15, 27, 28, 146 | 2, 4, 7, 13, 16, 26, 29, 42, 147 | 3, 8, 12, 17, 25, 30, 41, 43, 148 | 9, 11, 18, 24, 31, 40, 44, 53, 149 | 10, 19, 23, 32, 39, 45, 52, 54, 150 | 20, 22, 33, 38, 46, 51, 55, 60, 151 | 21, 34, 37, 47, 50, 56, 59, 61, 152 | 35, 36, 48, 49, 57, 58, 62, 63 153 | }; 154 | */ 155 | 156 | // 1:8 needs only DC 157 | // 1:4 needs coefficients 0,1,2,3 (actually more, but this is OK-ish) 158 | // 1:2 needs coefficients 0-16 (enough?) 159 | const int dct_cshifts[64] = { 160 | 3, 2, 2, 2, 1, 1, 1, 1, 161 | 1, 1, 1, 1, 1, 1, 1, 1, 162 | 163 | 0, 0, 0, 0, 0, 0, 0, 0, 164 | 0, 0, 0, 0, 0, 0, 0, 0, 165 | // 1, 1, 1, 1, 1, 1, 1, 1, 166 | // 1, 1, 1, 1, 0, 0, 0, 0, 167 | 0, 0, 0, 0, 0, 0, 0, 0, 168 | 0, 0, 0, 0, 0, 0, 0, 0, 169 | 0, 0, 0, 0, 0, 0, 0, 0, 170 | 0, 0, 0, 0, 0, 0, 0, 0, 171 | }; 172 | 173 | void default_DCT_scanscript(int nb_components, std::vector> & ordering, std::vector & comp, std::vector & coeff) { 174 | ordering.clear(); 175 | for(int i=0; i(64)); 177 | comp.clear(); 178 | coeff.clear(); 179 | std::vector pc(nb_components,0); 180 | std::vector cshifts(nb_components,3); 181 | int cc=0; 182 | int m=0; 183 | for (int p=0; p 63) {cc++; m=0;} 192 | else if (dct_cshifts[pc[cc]] < cshifts[cc]) { cshifts[cc]--; cc++; m=0;} 193 | else if (cshifts[cc] == 2 && ++m == 1) {cc++; m=0;} 194 | else if (cshifts[cc] == 1 && ++m == 1) {cc++; m=0;} 195 | else if (cshifts[cc] == 0 && ++m == 2) {cc++; m=0;} 196 | */ 197 | 198 | // tried this one, but doesn't seem to be better than doing the simpler thing 199 | // if (pc[cc] > 63) cc++; 200 | // else if (dct_cshifts[pc[cc]] < cshifts[cc]) { cshifts[cc]--; cc++; } 201 | 202 | // simple scan script: go one coefficient at a time, from low freq to high freq, alternating between the components 203 | cc++; 204 | if (cc == nb_components) { cc=0; } 205 | 206 | } 207 | } 208 | 209 | void default_DCT_parameters(std::vector ¶meters, const Image &image) { 210 | parameters.clear(); 211 | parameters.push_back(0); 212 | parameters.push_back(image.nb_channels - 1); 213 | } 214 | 215 | void meta_DCT(Image &image, std::vector ¶meters) { 216 | if (!parameters.size()) default_DCT_parameters(parameters,image); 217 | int beginc = image.nb_meta_channels + parameters[0]; 218 | int endc = image.nb_meta_channels + parameters[1]; 219 | int nb_channels = endc-beginc+1; 220 | std::vector> ordering; 221 | std::vector comp; 222 | std::vector coeff; 223 | default_DCT_scanscript(nb_channels,ordering,comp,coeff); 224 | for (int c=beginc; c<=endc; c++) { 225 | int bw = (image.channel[c].w+7)/8; 226 | int bh = (image.channel[c].h+7)/8; 227 | image.channel[c].w = bw; 228 | image.channel[c].h = bh; 229 | image.channel[c].hshift += 3; 230 | image.channel[c].vshift += 3; 231 | image.channel[c].hcshift += 3; 232 | image.channel[c].vcshift += 3; 233 | } 234 | for (int i=nb_channels; i<64*nb_channels; i++) { 235 | Channel dummy; 236 | int c = beginc+comp[i]; 237 | dummy.w=image.channel[c].w; dummy.h=image.channel[c].h; 238 | dummy.hshift = image.channel[c].hshift; 239 | dummy.vshift = image.channel[c].vshift; 240 | dummy.hcshift = dct_cshifts[coeff[i]]+image.channel[c].hcshift-3; 241 | dummy.vcshift = dct_cshifts[coeff[i]]+image.channel[c].vcshift-3; 242 | dummy.component = image.channel[c].component; 243 | assert(dummy.component == comp[i]); 244 | image.channel.push_back(dummy); 245 | } 246 | } 247 | 248 | // TODO: deal with partial inverse DCT for a 1:2 and 1:4 scale decode (it currently produces a 1:1 image) 249 | bool inv_DCT(Image &input, std::vector ¶meters) { 250 | if (!parameters.size()) default_DCT_parameters(parameters,input); 251 | int beginc = input.nb_meta_channels + parameters[0]; 252 | int endc = input.nb_meta_channels + parameters[1]; 253 | int nb_channels = endc-beginc+1; 254 | int offset = input.channel.size() - 63*nb_channels; 255 | 256 | if (offset <= endc) { 257 | e_printf("Invalid number of channels to apply inverse DCT.\n"); 258 | return false; 259 | } 260 | v_printf(3,"Undoing DCT on channels %i..%i with AC coefficients in channels %i..%i\n",beginc,endc,offset,offset+63*nb_channels-1); 261 | 262 | 263 | std::vector> ordering; 264 | std::vector comp; 265 | std::vector coeff; 266 | default_DCT_scanscript(nb_channels,ordering,comp,coeff); 267 | 268 | for (int c=beginc; c<=endc; c++) { 269 | int bw = input.channel[c-beginc+offset].w; // consider first AC, in case we did repeated DCT (TODO: try repeated DCT to see if it even makes sense) 270 | int bh = input.channel[c-beginc+offset].h; 271 | if (input.channel[c].w < bw) bw = input.channel[c].w; 272 | if (input.channel[c].h < bh) bh = input.channel[c].h; 273 | 274 | v_printf(3," Channel %i : %ix%i image from %ix%i blocks\n",c,bw*8,bh*8,bw,bh); 275 | Channel outch(bw*8,bh*8,0,0); 276 | outch.component = input.channel[c].component; 277 | outch.hshift = input.channel[c].hshift - 3; 278 | outch.vshift = input.channel[c].vshift - 3; 279 | outch.hcshift = input.channel[c].hcshift - 3; 280 | outch.vcshift = input.channel[c].hcshift - 3; 281 | float DCoffset = (input.maxval + 1.0) * 4.0; 282 | for (int by=0; by ¶meters) { 299 | Image tmp=input; // TODO: implement without copy 300 | std::vector adj_params = parameters; // use a copy so empty (default) parameters remain empty 301 | 302 | int beginc = input.nb_meta_channels + adj_params[0]; 303 | int endc = input.nb_meta_channels + adj_params[1]; 304 | int nb_channels = endc-beginc+1; 305 | int offset = input.channel.size(); 306 | meta_DCT(input, adj_params); 307 | 308 | v_printf(3,"Doing DCT on channels %i..%i with AC coefficients in channels %i..%i\n",beginc,endc,offset,offset+63*nb_channels-1); 309 | 310 | std::vector> ordering; 311 | std::vector comp; 312 | std::vector coeff; 313 | default_DCT_scanscript(nb_channels,ordering,comp,coeff); 314 | 315 | float DCoffset = (input.maxval + 1.0) * 4.0; 316 | 317 | for (int c=beginc; c>3),bx*8+(i&7)); 328 | ComputeBlockDCTDouble(block); 329 | input.channel[c].value(by,bx) = round(block[0]) - DCoffset; 330 | for (int i=1; i<64; i++) input.channel[offset-nb_channels+ ordering[c-beginc][jpeg_zigzag[i]]].value(by,bx) = round(block[i]); 331 | } 332 | } 333 | } 334 | 335 | return true; 336 | } 337 | 338 | 339 | 340 | bool DCT(Image &input, bool inverse, std::vector ¶meters) { 341 | if (inverse) return inv_DCT(input,parameters); 342 | else return fwd_DCT(input,parameters); 343 | } 344 | 345 | -------------------------------------------------------------------------------- /transform/palette.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | // TODO: allow this on metachannels too (useful for 2dmatch) 26 | 27 | #pragma once 28 | 29 | #include "../image/image.h" 30 | #include 31 | 32 | bool inv_palette(Image &input, std::vector parameters) { 33 | if (input.nb_meta_channels < 1) { 34 | e_printf("Error: Palette transform without palette.\n"); 35 | return false; 36 | } 37 | if (parameters.size() != 3) { 38 | e_printf("Error: Palette transform with incorrect parameters.\n"); 39 | return false; 40 | } 41 | int nb = input.channel[0].h; 42 | int c0 = input.nb_meta_channels + parameters[0]; 43 | if (c0 >= input.channel.size()) { 44 | e_printf("Error: Palette transform with incorrect parameters.\n"); 45 | return false; 46 | } 47 | int w = input.channel[c0].w; 48 | int h = input.channel[c0].h; 49 | // might be false in case of lossy 50 | //assert(input.channel[c0].minval == 0); 51 | //assert(input.channel[c0].maxval == palette.w-1); 52 | for (int i=1; i parameters) { 71 | if (parameters.size() != 3) { 72 | e_printf("Error: Palette transform with incorrect parameters.\n"); 73 | input.error = true; return; 74 | } 75 | int begin_c = input.nb_meta_channels+parameters[0]; 76 | int end_c = input.nb_meta_channels+parameters[1]; 77 | if (begin_c > end_c || end_c >= input.channel.size()) { 78 | e_printf("Error: Palette transform with incorrect parameters.\n"); 79 | input.error = true; return; 80 | } 81 | int nb = end_c - begin_c + 1; 82 | int &nb_colors = parameters[2]; 83 | input.nb_meta_channels++; 84 | input.nb_channels -= nb-1; 85 | input.channel.erase(input.channel.begin()+begin_c+1,input.channel.begin()+end_c+1); 86 | Channel pch(nb_colors,nb, 0, 1); 87 | pch.hshift = -1; 88 | input.channel.insert(input.channel.begin(),pch); 89 | } 90 | 91 | 92 | bool fwd_palette(Image &input, std::vector ¶meters) { 93 | assert(parameters.size() == 3); 94 | int begin_c = input.nb_meta_channels+parameters[0]; 95 | int end_c = input.nb_meta_channels+parameters[1]; 96 | int &nb_colors = parameters[2]; 97 | int nb = end_c - begin_c + 1; 98 | 99 | int w = input.channel[begin_c].w; 100 | int h = input.channel[begin_c].h; 101 | 102 | v_printf(8,"Trying to represent channels %i-%i using at most a %i-color palette.\n",begin_c,end_c,nb_colors); 103 | std::set< std::vector > candidate_palette; 104 | std::vector color(nb); 105 | for (int y=0; y nb_colors) return false; // too many colors 110 | } 111 | } 112 | nb_colors = candidate_palette.size(); 113 | v_printf(6,"Channels %i-%i can be represented using a %i-color palette.\n",begin_c,end_c,nb_colors); 114 | 115 | Channel pch(nb_colors,nb, 0, 1); 116 | pch.hshift = -1; 117 | int x=0; 118 | for (auto pcol : candidate_palette) { 119 | v_printf(9,"Color %i : ",x); 120 | for (int i=0; i ¶meters) { 147 | if (inverse) return inv_palette(input, parameters); 148 | else return fwd_palette(input, parameters); 149 | } 150 | 151 | -------------------------------------------------------------------------------- /transform/permute.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | 27 | #include "../image/image.h" 28 | #include "../io.h" 29 | 30 | 31 | bool inv_permute(Image &input, const std::vector ¶meters) { 32 | Image tmp=input; 33 | bool use_channel = (parameters.size()==0); 34 | int perm_length; 35 | if (use_channel) perm_length = input.channel[0].w; 36 | else perm_length = parameters.size(); 37 | 38 | v_printf(5,"Permutation: "); 39 | for (int i=0; i ¶meters, bool use_channel=false) { 57 | int nb = input.channel.size() - input.nb_meta_channels; 58 | if (parameters.size() == 0 || use_channel) { 59 | input.nb_meta_channels++; 60 | Channel pch(nb, 1, 0, nb-1); 61 | pch.hshift = -1; 62 | input.channel.insert(input.channel.begin(),pch); 63 | } else if (parameters.size() <= nb) { 64 | std::vector inchannel = input.channel; 65 | for (int i=0; i= parameters.size()) { 68 | e_printf("Invalid permutation: channel %i is lost\n",c); 69 | input.error=true; 70 | return; 71 | } 72 | for (int j=0; j %i] ",i,c); 79 | } 80 | } else { 81 | e_printf("Incorrect number of parameters in Permute transform.\n"); 82 | input.error=true; 83 | } 84 | } 85 | 86 | bool fwd_permute(Image &input, std::vector ¶meters) { 87 | // TODO: implement this without copying stuff 88 | Image tmp = input; 89 | if (parameters.size() < 3) { 90 | e_printf("Invalid permutation: not enough parameters\n"); 91 | return false; 92 | } 93 | bool use_channel = true; 94 | if (parameters[0] == -1) { 95 | parameters.erase(parameters.begin(), parameters.begin()+1); 96 | use_channel = false; 97 | } 98 | meta_permute(input, parameters, use_channel); 99 | if (!use_channel) return true; 100 | 101 | assert(input.channel[0].w == input.channel.size() - input.nb_meta_channels); 102 | if (parameters.size() != input.channel[0].w) { 103 | e_printf("Invalid permutation: need to specify %i new channels, gave only %i\n",input.channel[0].w,parameters.size()); 104 | return false; 105 | } 106 | v_printf(5,"Permutation: "); 107 | for (int i=0; i= input.channel[0].w) { 111 | e_printf("Invalid permutation: %i is not a channel number\n",c); 112 | return false; 113 | } 114 | for (int j=0; j %i] ",i,c); 120 | } 121 | v_printf(5,"\n"); 122 | return true; 123 | } 124 | 125 | bool permute(Image &input, bool inverse, std::vector ¶meters) { 126 | if (inverse) return inv_permute(input, parameters); 127 | else return fwd_permute(input, parameters); 128 | } 129 | -------------------------------------------------------------------------------- /transform/quantize.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | 27 | #include "../image/image.h" 28 | #include "../io.h" 29 | 30 | 31 | 32 | bool inv_quantize(Image &input, const std::vector ¶meters) { 33 | for (int c=input.nb_meta_channels; c ¶meters) { 57 | for (int c=input.nb_meta_channels; c ¶meters) { 74 | if (inverse) return inv_quantize(input, parameters); 75 | else return fwd_quantize(input, parameters); 76 | } 77 | 78 | -------------------------------------------------------------------------------- /transform/subsample.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | 27 | #include "../image/image.h" 28 | #include "../io.h" 29 | 30 | // JPEG-style (chroma) subsampling. Parameters are: [begin_channel], [end_channel], [sample_ratio_h], [sample_ratio_v], ... 31 | // e.g. 1, 2, 2 corresponds to 4:2:0 32 | 33 | void check_subsample_parameters(std::vector ¶meters) { 34 | if (parameters.size() == 1) { 35 | // special case: abbreviated parameters for some common cases 36 | switch (parameters[0]) { 37 | case 0: // 4:2:0 38 | parameters[0] = 1; 39 | parameters.push_back(2); 40 | parameters.push_back(2); 41 | parameters.push_back(2); 42 | break; 43 | case 1: // 4:2:2 44 | parameters[0] = 1; 45 | parameters.push_back(2); 46 | parameters.push_back(2); 47 | parameters.push_back(1); 48 | break; 49 | case 2: // 4:4:0 50 | parameters[0] = 1; 51 | parameters.push_back(2); 52 | parameters.push_back(1); 53 | parameters.push_back(2); 54 | break; 55 | case 3: // 4:1:1 56 | parameters[0] = 1; 57 | parameters.push_back(2); 58 | parameters.push_back(4); 59 | parameters.push_back(1); 60 | break; 61 | default: 62 | break; 63 | } 64 | } 65 | if (parameters.size() % 4) { 66 | e_printf("Error: invalid parameters for subsampling.\n"); 67 | parameters.clear(); 68 | } 69 | } 70 | 71 | 72 | // simple upscaling; TODO: fancy upscaling 73 | bool inv_subsample(Image &input, std::vector parameters) { 74 | check_subsample_parameters(parameters); 75 | 76 | for (int i=0; i= input.channel[input.nb_meta_channels].w && oh >= input.channel[input.nb_meta_channels].h) { 85 | // this can happen in case of LQIP and 1:16 scale decodes 86 | v_printf(5,"Skipping upscaling of channel %i because it is already as large as channel %i.\n",c,input.nb_meta_channels); 87 | continue; 88 | } 89 | Channel channel(ow*srh,oh*srv,input.channel[c].minval,input.channel[c].maxval); 90 | if (srv <= 2 && srh <= 2) { 91 | // 'fancy' horizontal upscale 92 | if (srh == 2) { 93 | for (int y=0; y>2; 96 | channel.value(y*srv,x*srh+1) = (3*input.channel[c].value(y,x) + input.channel[c].value(y,(x+1>2; 97 | } 98 | } 99 | } else { 100 | for (int y=0; y>2; 111 | channel.value(y*srv+1,x) = (3*orig.value(y*srv,x) + orig.value((y+1>2; 112 | } 113 | } 114 | } 115 | } else { 116 | // simple box filter upscaling. fancier upscaling could be done too... 117 | for (int y=0; y ¶meters) { 131 | return false; // TODO (not really needed though; subsampling is useful if the input data is a JPEG or YUV, but then the transform is already done) 132 | // for non-subsampled input it's probably better to just stick to 4:4:4 (and quantize most of the chroma details away) 133 | } 134 | 135 | void meta_subsample(Image &input, std::vector parameters) { 136 | check_subsample_parameters(parameters); 137 | if (parameters.size()) 138 | for (int i=0; i ¶meters) { 160 | if (inverse) return inv_subsample(input, parameters); 161 | else return fwd_subsample(input, parameters); 162 | } 163 | 164 | -------------------------------------------------------------------------------- /transform/transform.cpp: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #include "../image/image.h" 26 | #include "transform.h" 27 | 28 | // transforms needed to represent legacy JPEG 29 | #include "ycbcr.h" 30 | #include "subsample.h" 31 | #include "dct.h" 32 | #include "quantize.h" 33 | 34 | // new transforms 35 | #include "ycocg.h" 36 | #include "palette.h" 37 | #include "squeeze.h" 38 | #include "2dmatch.h" 39 | #include "permute.h" 40 | #include "approximate.h" 41 | 42 | //#include "xyb.h" 43 | 44 | 45 | const std::vector transform_name = {"YCbCr", "YCoCg", "ICtCp [TODO]", "ChromaSubsampling", "DCT", "Quantization", "Palette", "Squeeze", "Matching", "Permutation", "Approximation", "XYB"}; 46 | 47 | 48 | bool Transform::apply(Image &input, bool inverse) { 49 | switch(ID) { 50 | case TRANSFORM_YCbCr: return YCbCr(input, inverse); 51 | case TRANSFORM_ChromaSubsample: return subsample(input, inverse, parameters); 52 | case TRANSFORM_DCT: return DCT(input, inverse, parameters); 53 | case TRANSFORM_QUANTIZE: return quantize(input, inverse, parameters); 54 | case TRANSFORM_YCoCg: return YCoCg(input, inverse); 55 | // case TRANSFORM_XYB: return XYB(input, inverse); 56 | case TRANSFORM_SQUEEZE: return squeeze(input, inverse, parameters); 57 | case TRANSFORM_PALETTE: return palette(input, inverse, parameters); 58 | case TRANSFORM_2DMATCH: return match(input, inverse, parameters); 59 | case TRANSFORM_PERMUTE: return permute(input, inverse, parameters); 60 | case TRANSFORM_APPROXIMATE: return approximate(input, inverse, parameters); 61 | default: e_printf("Unknown transformation (ID=%i)\n",ID); return false; 62 | } 63 | } 64 | 65 | 66 | void Transform::meta_apply(Image &input) { 67 | switch(ID) { 68 | case TRANSFORM_YCbCr: return; 69 | case TRANSFORM_YCoCg: return; 70 | // case TRANSFORM_XYB: return; 71 | case TRANSFORM_ChromaSubsample: meta_subsample(input, parameters); return; 72 | case TRANSFORM_DCT: meta_DCT(input, parameters); return; 73 | case TRANSFORM_QUANTIZE: return; 74 | case TRANSFORM_SQUEEZE: meta_squeeze(input, parameters); return; 75 | case TRANSFORM_PALETTE: meta_palette(input, parameters); return; 76 | case TRANSFORM_2DMATCH: meta_match(input, parameters); return; 77 | case TRANSFORM_PERMUTE: meta_permute(input, parameters); return; 78 | case TRANSFORM_APPROXIMATE: meta_approximate(input, parameters); return; 79 | default: e_printf("Unknown transformation (ID=%i)\n",ID); return; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /transform/transform.h: -------------------------------------------------------------------------------- 1 | /*////////////////////////////////////////////////////////////////////////////////////////////////////// 2 | 3 | Copyright 2019, Jon Sneyers, Cloudinary (jon@cloudinary.com) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | //////////////////////////////////////////////////////////////////////////////////////////////////////*/ 24 | 25 | #pragma once 26 | 27 | #include "../image/image.h" 28 | 29 | // JPEG-style RGB to YCbCr color transformation 30 | #define TRANSFORM_YCbCr 0 31 | 32 | // Lossless YCoCg color transformation 33 | #define TRANSFORM_YCoCg 1 34 | 35 | 36 | // XYB color transformation from PIK 37 | #define TRANSFORM_XYB 11 38 | 39 | // Rec. 2100 ICtCp color transformation 40 | // (TO BE IMPLEMENTED) 41 | #define TRANSFORM_ICtCp 2 42 | 43 | // JPEG-style (chroma) subsampling. Parameters are: [nb_subsampled_channels], [channel], [sample_ratio_h], [sample_ratio_v], ... 44 | // e.g. 2, 1, 2, 2, 2, 2, 2 corresponds to 4:2:0 45 | #define TRANSFORM_ChromaSubsample 3 46 | 47 | // JPEG-style 8x8 DCT 48 | #define TRANSFORM_DCT 4 49 | // some of the internals are needed to do JPEG import (and export, TODO) 50 | extern const int jpeg_zigzag[64]; 51 | extern const int dct_cshifts[64]; 52 | void default_DCT_scanscript(int nb_components, std::vector> & ordering, std::vector & comp, std::vector & coeff); 53 | 54 | // JPEG-style quantization. Parameters are quantization factors for each channel (encoded as part of channel metadata). 55 | #define TRANSFORM_QUANTIZE 5 56 | 57 | // Color palette. Parameters are: [begin_c] [end_c] [max colors] 58 | #define TRANSFORM_PALETTE 6 59 | 60 | // Squeezing (Haar-style) 61 | #define TRANSFORM_SQUEEZE 7 62 | 63 | // 2D Matching 64 | #define TRANSFORM_2DMATCH 8 65 | 66 | // Channel permutation. Has to be the last transform in the chain if used without parameters. 67 | #define TRANSFORM_PERMUTE 9 68 | 69 | // Approximation, i.e. lossless quantization (remainders are put in new channel) 70 | #define TRANSFORM_APPROXIMATE 10 71 | 72 | #include 73 | 74 | 75 | extern const std::vector transform_name; 76 | 77 | class Transform { 78 | public: 79 | const int ID; 80 | std::vector parameters; 81 | 82 | Transform(int id) : ID(id) {} 83 | bool apply(Image &input, bool inverse); 84 | void meta_apply(Image &input); 85 | bool has_parameters() const { 86 | switch(ID) { 87 | case TRANSFORM_ChromaSubsample: 88 | case TRANSFORM_PALETTE: // stores palette itself as meta-channel, but parameter says which channel(s) 89 | case TRANSFORM_SQUEEZE: 90 | case TRANSFORM_DCT: 91 | case TRANSFORM_2DMATCH: 92 | case TRANSFORM_PERMUTE: // stores permutation as meta-channel, or in parameters 93 | case TRANSFORM_APPROXIMATE: 94 | return true; 95 | case TRANSFORM_YCbCr: // 96 | case TRANSFORM_YCoCg: // color transforms always work on channels 0-2 97 | case TRANSFORM_ICtCp: // 98 | case TRANSFORM_XYB: // 99 | default: 100 | return false; 101 | } 102 | } 103 | const char * name() const { 104 | return transform_name[ID].c_str(); 105 | } 106 | }; 107 | -------------------------------------------------------------------------------- /transform/xyb.h: -------------------------------------------------------------------------------- 1 | 2 | #pragma once 3 | 4 | #include "../image/image.h" 5 | #include "../io.h" 6 | #include "../config.h" 7 | 8 | #define SRGB_TO_LINEAR(c) ((c <= 0.04045 ? c / 12.92 : pow((c + 0.055) / 1.055, 2.4))) 9 | #define LINEAR_TO_SRGB(c) ((c < 0.0031308 ? 12.92 * c : 1.055 * pow(c,1.0/2.4) - 0.055)) 10 | #define X_PRECISION 50.0 11 | #define Y_PRECISION 2.0 12 | #define B_PRECISION 2.0 13 | 14 | static const float kOpsinAbsorbanceMatrix[9] = { 15 | 0.29758629764381878, 0.63479886329551205, 0.088129079251512379, 16 | 0.22671198744507498, 0.6936230820580469, 0.098933489737625696, 17 | 0.19161912544122028, 0.082898111024512638, 0.53811869403330603 18 | }; 19 | 20 | static const float kOpsinAbsorbanceBias[3] = { 0.22909219828963151, 0.22754880685562834, 0.1426315625332778 }; 21 | 22 | static const float kOpsinAbsorbanceInvMatrix[9] = { 23 | 11.006452774923291, -10.079060487634042, 0.050487148542225385, 24 | -3.106722218699964, 4.319048293702604, -0.2852641120243108, 25 | -3.4407008566370068, 2.923704060800807, 1.8842934914233993 26 | }; 27 | 28 | 29 | bool inv_XYB(Image &input) ATTRIBUTE_HOT; 30 | bool inv_XYB(Image &input) { 31 | int nb_channels = input.channel.size(); 32 | if (nb_channels < 3) { 33 | e_printf("Invalid number of channels to apply inverse XYB.\n"); 34 | return false; 35 | } 36 | int w = input.channel[0].w; 37 | int h = input.channel[0].h; 38 | if (input.channel[1].w < w 39 | || input.channel[1].h < h 40 | || input.channel[2].w < w 41 | || input.channel[2].h < h) { 42 | e_printf("Invalid channel dimensions to apply inverse XYB (maybe chroma is subsampled?).\n"); 43 | return false; 44 | } 45 | 46 | for (int y=0; y>1), 0, input.maxval); 55 | int B = CLAMP(Y + ((1-Cg)>>1) - (Co>>1), 0, input.maxval); 56 | int R = CLAMP(Co + B, 0, input.maxval); 57 | input.channel[m+0].value(y,x) = R; 58 | input.channel[m+1].value(y,x) = G; 59 | input.channel[m+2].value(y,x) = B; 60 | } 61 | } 62 | return true; 63 | } 64 | 65 | bool fwd_YCoCg(Image &input) { 66 | int nb_channels = input.nb_channels; 67 | if (nb_channels < 3) { 68 | //e_printf("Invalid number of channels to apply YCoCg.\n"); 69 | return false; 70 | } 71 | int m = input.nb_meta_channels; 72 | int w = input.channel[m+0].w; 73 | int h = input.channel[m+0].h; 74 | if (input.channel[m+1].w < w 75 | || input.channel[m+1].h < h 76 | || input.channel[m+2].w < w 77 | || input.channel[m+2].h < h) { 78 | e_printf("Invalid channel dimensions to apply YCoCg.\n"); 79 | return false; 80 | } 81 | for (int y=0; y>1) + G)>>1; 87 | int Co = R - B; 88 | int Cg = G - ((R + B)>>1); 89 | input.channel[m+0].value(y,x) = Y; 90 | input.channel[m+1].value(y,x) = Co; 91 | input.channel[m+2].value(y,x) = Cg; 92 | } 93 | } 94 | return true; 95 | } 96 | 97 | 98 | bool YCoCg(Image &input, bool inverse) { 99 | if (inverse) return inv_YCoCg(input); 100 | else return fwd_YCoCg(input); 101 | } 102 | -------------------------------------------------------------------------------- /util.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define CLAMP(x,l,u) ((xu?u:x))) 4 | #define MIN(A, B) ((A) < (B) ? (A) : (B)) 5 | #define MAX(A, B) ((A) > (B) ? (A) : (B)) 6 | 7 | 8 | 9 | template I inline median3(I a, I b, I c) { 10 | if (a < b) { 11 | if (b < c) { 12 | return b; 13 | } else { 14 | return a < c ? c : a; 15 | } 16 | } else { 17 | if (a < c) { 18 | return a; 19 | } else { 20 | return b < c ? c : b; 21 | } 22 | } 23 | } 24 | --------------------------------------------------------------------------------