1 /*
2 * Copyright 2020 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 // This program converts an image from stdin (e.g. a JPEG, PNG, etc.) to stdout
9 // (in the NIA/NIE format, a trivial image file format).
10 //
11 // The NIA/NIE file format specification is at:
12 // https://github.com/google/wuffs/blob/master/doc/spec/nie-spec.md
13 //
14 // Pass "-1" or "-first-frame-only" as a command line flag to output NIE (a
15 // still image) instead of NIA (an animated image). The output format (NIA or
16 // NIE) depends only on this flag's absence or presence, not on the stdin
17 // image's format.
18 //
19 // There are multiple codec implementations of any given image format. For
20 // example, as of May 2020, Chromium, Skia and Wuffs each have their own BMP
21 // decoder implementation. There is no standard "libbmp" that they all share.
22 // Comparing this program's output (or hashed output) to similar programs in
23 // other repositories can identify image inputs for which these decoders (or
24 // different versions of the same decoder) produce different output (pixels).
25 //
26 // An equivalent program (using the Chromium image codecs) is at:
27 // https://crrev.com/c/2210331
28 //
29 // An equivalent program (using the Wuffs image codecs) is at:
30 // https://github.com/google/wuffs/blob/master/example/convert-to-nia/convert-to-nia.c
31
32 #include <stdio.h>
33 #include <string.h>
34
35 #include "include/codec/SkCodec.h"
36 #include "include/core/SkBitmap.h"
37 #include "include/core/SkData.h"
38 #include "src/base/SkAutoMalloc.h"
39
set_u32le(uint8_t * ptr,uint32_t val)40 static inline void set_u32le(uint8_t* ptr, uint32_t val) {
41 ptr[0] = val >> 0;
42 ptr[1] = val >> 8;
43 ptr[2] = val >> 16;
44 ptr[3] = val >> 24;
45 }
46
set_u64le(uint8_t * ptr,uint64_t val)47 static inline void set_u64le(uint8_t* ptr, uint64_t val) {
48 ptr[0] = val >> 0;
49 ptr[1] = val >> 8;
50 ptr[2] = val >> 16;
51 ptr[3] = val >> 24;
52 ptr[4] = val >> 32;
53 ptr[5] = val >> 40;
54 ptr[6] = val >> 48;
55 ptr[7] = val >> 56;
56 }
57
write_nix_header(uint32_t magicU32le,uint32_t width,uint32_t height)58 static void write_nix_header(uint32_t magicU32le, uint32_t width, uint32_t height) {
59 uint8_t data[16];
60 set_u32le(data + 0, magicU32le);
61 set_u32le(data + 4, 0x346E62FF); // 4 bytes per pixel non-premul BGRA.
62 set_u32le(data + 8, width);
63 set_u32le(data + 12, height);
64 fwrite(data, 1, 16, stdout);
65 }
66
write_nia_duration(uint64_t totalDurationMillis)67 static bool write_nia_duration(uint64_t totalDurationMillis) {
68 // Flicks are NIA's unit of time. One flick (frame-tick) is 1 / 705_600_000
69 // of a second. See https://github.com/OculusVR/Flicks
70 static constexpr uint64_t flicksPerMilli = 705600;
71 if (totalDurationMillis > (INT64_MAX / flicksPerMilli)) {
72 // Converting from millis to flicks would overflow.
73 return false;
74 }
75
76 uint8_t data[8];
77 set_u64le(data + 0, totalDurationMillis * flicksPerMilli);
78 fwrite(data, 1, 8, stdout);
79 return true;
80 }
81
write_nie_pixels(uint32_t width,uint32_t height,const SkBitmap & bm)82 static void write_nie_pixels(uint32_t width, uint32_t height, const SkBitmap& bm) {
83 static constexpr size_t kBufferSize = 4096;
84 uint8_t buf[kBufferSize];
85 size_t n = 0;
86 for (uint32_t y = 0; y < height; y++) {
87 for (uint32_t x = 0; x < width; x++) {
88 SkColor c = bm.getColor(x, y);
89 buf[n++] = SkColorGetB(c);
90 buf[n++] = SkColorGetG(c);
91 buf[n++] = SkColorGetR(c);
92 buf[n++] = SkColorGetA(c);
93 if (n == kBufferSize) {
94 fwrite(buf, 1, n, stdout);
95 n = 0;
96 }
97 }
98 }
99 if (n > 0) {
100 fwrite(buf, 1, n, stdout);
101 }
102 }
103
write_nia_padding(uint32_t width,uint32_t height)104 static void write_nia_padding(uint32_t width, uint32_t height) {
105 // 4 bytes of padding when the width and height are both odd.
106 if (width & height & 1) {
107 uint8_t data[4];
108 set_u32le(data + 0, 0);
109 fwrite(data, 1, 4, stdout);
110 }
111 }
112
write_nia_footer(int repetitionCount,bool stillImage)113 static void write_nia_footer(int repetitionCount, bool stillImage) {
114 uint8_t data[8];
115 if (stillImage || (repetitionCount == SkCodec::kRepetitionCountInfinite)) {
116 set_u32le(data + 0, 0);
117 } else {
118 // NIA's loop count and Skia's repetition count differ by one. See
119 // https://github.com/google/wuffs/blob/master/doc/spec/nie-spec.md#nii-footer
120 set_u32le(data + 0, 1 + repetitionCount);
121 }
122 set_u32le(data + 4, 0x80000000);
123 fwrite(data, 1, 8, stdout);
124 }
125
main(int argc,char ** argv)126 int main(int argc, char** argv) {
127 bool firstFrameOnly = false;
128 for (int a = 1; a < argc; a++) {
129 if ((strcmp(argv[a], "-1") == 0) || (strcmp(argv[a], "-first-frame-only") == 0)) {
130 firstFrameOnly = true;
131 break;
132 }
133 }
134
135 std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(SkData::MakeFromFILE(stdin)));
136 if (!codec) {
137 SkDebugf("Decode failed.\n");
138 return 1;
139 }
140 codec->getInfo().makeColorSpace(nullptr);
141 SkBitmap bm;
142 bm.allocPixels(codec->getInfo());
143 size_t bmByteSize = bm.computeByteSize();
144
145 // Cache a frame that future frames may depend on.
146 int cachedFrame = SkCodec::kNoFrame;
147 SkAutoMalloc cachedFramePixels;
148
149 uint64_t totalDurationMillis = 0;
150 const int frameCount = codec->getFrameCount();
151 if (frameCount == 0) {
152 SkDebugf("No frames.\n");
153 return 1;
154 }
155 std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
156 bool stillImage = frameInfos.size() <= 1;
157
158 for (int i = 0; i < frameCount; i++) {
159 SkCodec::Options opts;
160 opts.fFrameIndex = i;
161
162 if (!stillImage) {
163 int durationMillis = frameInfos[i].fDuration;
164 if (durationMillis < 0) {
165 SkDebugf("Negative animation duration.\n");
166 return 1;
167 }
168 totalDurationMillis += static_cast<uint64_t>(durationMillis);
169 if (totalDurationMillis > INT64_MAX) {
170 SkDebugf("Unsupported animation duration.\n");
171 return 1;
172 }
173
174 if ((cachedFrame != SkCodec::kNoFrame) &&
175 (cachedFrame == frameInfos[i].fRequiredFrame) && cachedFramePixels.get()) {
176 opts.fPriorFrame = cachedFrame;
177 memcpy(bm.getPixels(), cachedFramePixels.get(), bmByteSize);
178 }
179 }
180
181 if (!firstFrameOnly) {
182 if (i == 0) {
183 write_nix_header(0x41AFC36E, // "nïA" magic string as a u32le.
184 bm.width(), bm.height());
185 }
186
187 if (!write_nia_duration(totalDurationMillis)) {
188 SkDebugf("Unsupported animation duration.\n");
189 return 1;
190 }
191 }
192
193 const SkCodec::Result result =
194 codec->getPixels(codec->getInfo(), bm.getPixels(), bm.rowBytes(), &opts);
195 if ((result != SkCodec::kSuccess) && (result != SkCodec::kIncompleteInput)) {
196 SkDebugf("Decode frame pixels #%d failed.\n", i);
197 return 1;
198 }
199
200 // If the next frame depends on this one, store it in cachedFrame. It
201 // is possible that we may discard a frame that future frames depend
202 // on, but the codec will simply redecode the discarded frame.
203 if ((static_cast<size_t>(i + 1) < frameInfos.size()) &&
204 (frameInfos[i + 1].fRequiredFrame == i)) {
205 cachedFrame = i;
206 memcpy(cachedFramePixels.reset(bmByteSize), bm.getPixels(), bmByteSize);
207 }
208
209 int width = bm.width();
210 int height = bm.height();
211 write_nix_header(0x45AFC36E, // "nïE" magic string as a u32le.
212 width, height);
213 write_nie_pixels(width, height, bm);
214 if (result == SkCodec::kIncompleteInput) {
215 SkDebugf("Incomplete input.\n");
216 return 1;
217 }
218 if (firstFrameOnly) {
219 return 0;
220 }
221 write_nia_padding(width, height);
222 }
223 write_nia_footer(codec->getRepetitionCount(), stillImage);
224 return 0;
225 }
226