Line data Source code
1 : /**
2 : Copyright (c) 2022 Roman Katuntsev <sbkarr@stappler.org>
3 : Copyright (c) 2023 Stappler LLC <admin@stappler.dev>
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
13 : all 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
21 : THE SOFTWARE.
22 : **/
23 :
24 : #include "SPBitmapFormat.h"
25 : #include "SPLog.h"
26 : #include "SPFilesystem.h"
27 : #include "gif_lib.h"
28 :
29 : namespace STAPPLER_VERSIONIZED stappler::bitmap::gif {
30 :
31 2875 : static bool isGif(const uint8_t * data, size_t dataLen) {
32 2875 : if (dataLen <= 8) {
33 0 : return false;
34 : }
35 :
36 : static const unsigned char GIF_SIGNATURE_1[] = {0x47, 0x49, 0x46, 0x38, 0x37, 0x61};
37 : static const unsigned char GIF_SIGNATURE_2[] = {0x47, 0x49, 0x46, 0x38, 0x39, 0x61};
38 2875 : return memcmp(GIF_SIGNATURE_1, data, sizeof(GIF_SIGNATURE_1)) == 0
39 2875 : || memcmp(GIF_SIGNATURE_2, data, sizeof(GIF_SIGNATURE_2)) == 0;
40 : }
41 :
42 575 : static bool getGifImageSize(const io::Producer &file, StackBuffer<512> &data, uint32_t &width, uint32_t &height) {
43 575 : if (isGif(data.data(), data.size())) {
44 375 : auto reader = BytesViewTemplate<Endian::Little>(data.data() + 6, 4);
45 :
46 375 : width = reader.readUnsigned16();
47 375 : height = reader.readUnsigned16();
48 :
49 375 : return true;
50 : }
51 200 : return false;
52 : }
53 :
54 396100 : static int Gif_InputFunc(GifFileType *file, GifByteType *bytes, int count) {
55 396100 : auto reader = (CoderSource *)file->UserData;
56 :
57 396100 : if (count >= 0) {
58 396100 : return reader->read(bytes, count);
59 : }
60 0 : return 0;
61 : }
62 :
63 : struct GifReadStruct {
64 250 : ~GifReadStruct() {
65 250 : if (!file) {
66 0 : DGifCloseFile(file, &error);
67 0 : file = nullptr;
68 : }
69 250 : }
70 :
71 250 : GifReadStruct() { }
72 :
73 250 : bool init(const uint8_t *inputData, size_t size) {
74 250 : reader._data = BytesViewTemplate<Endian::Network>(inputData, size);
75 250 : file = DGifOpen((void *)&reader, &Gif_InputFunc, &error);
76 :
77 250 : if (!file || error != 0) {
78 0 : log::error("GIF", "fail to open file");
79 0 : return false;
80 : }
81 :
82 250 : if (DGifSlurp(file) != GIF_OK) {
83 0 : log::error("GIF", "fail to read file");
84 0 : return false;
85 : }
86 :
87 250 : if (file->ImageCount == 0) {
88 0 : log::error("GIF", "no images found");
89 0 : return false;
90 : }
91 :
92 250 : return true;
93 : }
94 :
95 250 : bool info(ImageInfo &outputData) {
96 250 : ColorMapObject *colors = (file->SavedImages->ImageDesc.ColorMap) ? file->SavedImages->ImageDesc.ColorMap : file->SColorMap;
97 250 : if (!colors) {
98 0 : log::error("GIF", "no color profile found");
99 0 : return false;
100 : }
101 :
102 13350 : auto checkGrayscale = [&] (GifColorType &c) {
103 13350 : return c.Red == c.Green && c.Red == c.Blue;
104 : };
105 :
106 250 : bool isGrayscale = true;
107 13450 : for (size_t i = 0; i < size_t(colors->ColorCount); ++ i) {
108 13350 : if (!checkGrayscale(colors->Colors[i])) {
109 150 : isGrayscale = false;
110 150 : break;
111 : }
112 : }
113 :
114 250 : if (file->ExtensionBlockCount > 0) {
115 0 : for (size_t i = 0; i < size_t(file->ExtensionBlockCount); ++ i) {
116 : GraphicsControlBlock GCB;
117 0 : if (DGifExtensionToGCB(file->ExtensionBlocks[i].ByteCount, file->ExtensionBlocks[i].Bytes, &GCB) == GIF_OK) {
118 0 : if (GCB.TransparentColor != NO_TRANSPARENT_COLOR) {
119 0 : transparent = GCB.TransparentColor;
120 : }
121 : }
122 : }
123 : }
124 :
125 250 : if (file->SavedImages->ExtensionBlockCount > 0) {
126 : GraphicsControlBlock GCB;
127 250 : if (DGifSavedExtensionToGCB(file, 0, &GCB) == GIF_OK) {
128 250 : if (GCB.TransparentColor != NO_TRANSPARENT_COLOR) {
129 100 : transparent = GCB.TransparentColor;
130 : }
131 : }
132 : }
133 :
134 250 : outputData.width = file->SavedImages->ImageDesc.Width;
135 250 : outputData.height = file->SavedImages->ImageDesc.Height;
136 :
137 250 : outputData.color = (transparent != maxOf<size_t>())
138 400 : ? (isGrayscale ? PixelFormat::IA88 : PixelFormat::RGBA8888)
139 150 : : (isGrayscale ? ((outputData.color == PixelFormat::A8) ? PixelFormat::A8 : PixelFormat::I8) : PixelFormat::RGB888);
140 :
141 400 : outputData.alpha = (transparent != maxOf<size_t>() || outputData.color == PixelFormat::A8)
142 400 : ? AlphaFormat::Unpremultiplied
143 : : AlphaFormat::Opaque;
144 :
145 250 : outputData.stride = max(outputData.stride, (uint32_t)(outputData.width * getBytesPerPixel(outputData.color)));
146 :
147 250 : return true;
148 : }
149 :
150 125 : bool load(BitmapWriter &outputData) {
151 125 : if (!info(outputData)) {
152 0 : return false;
153 : }
154 :
155 125 : ColorMapObject *colors = (file->SavedImages->ImageDesc.ColorMap) ? file->SavedImages->ImageDesc.ColorMap : file->SColorMap;
156 125 : if (!colors) {
157 0 : log::error("GIF", "no color profile found");
158 0 : return false;
159 : }
160 :
161 125 : if (outputData.getStride) {
162 125 : outputData.stride = (uint32_t)outputData.getStride(outputData.target, outputData.color, outputData.width);
163 : }
164 :
165 125 : auto dataLen = outputData.stride * outputData.height;
166 125 : outputData.resize(outputData.target, dataLen);
167 :
168 125 : auto input = file->SavedImages->RasterBits;
169 125 : auto location = outputData.getData(outputData.target, 0);
170 :
171 125 : if (outputData.color == PixelFormat::RGB888) {
172 15650 : for (size_t i = 0; i < outputData.height; ++ i) {
173 15600 : auto loc = location;
174 4882800 : for (size_t j = 0; j < outputData.width; ++ j) {
175 4867200 : auto &c = colors->Colors[input[i * outputData.width + j]];
176 4867200 : *loc++ = c.Red;
177 4867200 : *loc++ = c.Green;
178 4867200 : *loc++ = c.Blue;
179 : }
180 15600 : location += outputData.stride;
181 : }
182 75 : } else if (outputData.color == PixelFormat::A8 || outputData.color == PixelFormat::I8) {
183 5650 : for (size_t i = 0; i < outputData.height; ++ i) {
184 5625 : auto loc = location;
185 2255625 : for (size_t j = 0; j < outputData.width; ++ j) {
186 2250000 : auto &c = colors->Colors[input[i * outputData.width + j]];
187 2250000 : *loc++ = c.Red;
188 : }
189 5625 : location += outputData.stride;
190 : }
191 75 : } else if (outputData.color == PixelFormat::IA88) {
192 3775 : for (size_t i = 0; i < outputData.height; ++ i) {
193 3750 : auto loc = location;
194 566250 : for (size_t j = 0; j < outputData.width; ++ j) {
195 562500 : auto idx = input[i * outputData.width + j];
196 562500 : *loc++ = colors->Colors[idx].Red;
197 562500 : if (idx == transparent) {
198 540850 : *loc++ = 0;
199 : } else {
200 21650 : *loc++ = 255;
201 : }
202 : }
203 3750 : location += outputData.stride;
204 : }
205 25 : } else if (outputData.color == PixelFormat::RGBA8888) {
206 8775 : for (size_t i = 0; i < outputData.height; ++ i) {
207 8750 : auto loc = location;
208 1758750 : for (size_t j = 0; j < outputData.width; ++ j) {
209 1750000 : auto idx = input[i * outputData.width + j];
210 1750000 : auto &c = colors->Colors[idx];
211 1750000 : *loc++ = c.Red;
212 1750000 : *loc++ = c.Green;
213 1750000 : *loc++ = c.Blue;
214 1750000 : if (idx == transparent) {
215 1738600 : *loc++ = 0;
216 : } else {
217 11400 : *loc++ = 255;
218 : }
219 : }
220 8750 : location += outputData.stride;
221 : }
222 : }
223 :
224 125 : return true;
225 : }
226 :
227 : int error = 0;
228 : GifFileType *file = nullptr;
229 : CoderSource reader;
230 : size_t transparent = maxOf<size_t>();
231 : };
232 :
233 125 : SPUNUSED static bool infoGif(const uint8_t *inputData, size_t size, ImageInfo &outputData) {
234 125 : GifReadStruct readStruct;
235 250 : return readStruct.init(inputData, size) && readStruct.info(outputData);
236 125 : }
237 :
238 125 : SPUNUSED static bool loadGif(const uint8_t *inputData, size_t size, BitmapWriter &outputData) {
239 125 : GifReadStruct readStruct;
240 250 : return readStruct.init(inputData, size) && readStruct.load(outputData);
241 125 : }
242 :
243 : }
|