• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/core/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     // The SkCodec::getFrameInfo comment says that this vector will be empty
156     // for still (not animated) images, even though frameCount should be 1.
157     std::vector<SkCodec::FrameInfo> frameInfos = codec->getFrameInfo();
158     bool                            stillImage = frameInfos.empty();
159 
160     for (int i = 0; i < frameCount; i++) {
161         SkCodec::Options opts;
162         opts.fFrameIndex = i;
163 
164         if (!stillImage) {
165             int durationMillis = frameInfos[i].fDuration;
166             if (durationMillis < 0) {
167                 SkDebugf("Negative animation duration.\n");
168                 return 1;
169             }
170             totalDurationMillis += static_cast<uint64_t>(durationMillis);
171             if (totalDurationMillis > INT64_MAX) {
172                 SkDebugf("Unsupported animation duration.\n");
173                 return 1;
174             }
175 
176             if ((cachedFrame != SkCodec::kNoFrame) &&
177                 (cachedFrame == frameInfos[i].fRequiredFrame) && cachedFramePixels.get()) {
178                 opts.fPriorFrame = cachedFrame;
179                 memcpy(bm.getPixels(), cachedFramePixels.get(), bmByteSize);
180             }
181         }
182 
183         if (!firstFrameOnly) {
184             if (i == 0) {
185                 write_nix_header(0x41AFC36E,  // "nïA" magic string as a u32le.
186                                  bm.width(), bm.height());
187             }
188 
189             if (!write_nia_duration(totalDurationMillis)) {
190                 SkDebugf("Unsupported animation duration.\n");
191                 return 1;
192             }
193         }
194 
195         const SkCodec::Result result =
196             codec->getPixels(codec->getInfo(), bm.getPixels(), bm.rowBytes(), &opts);
197         if ((result != SkCodec::kSuccess) && (result != SkCodec::kIncompleteInput)) {
198             SkDebugf("Decode frame pixels #%d failed.\n", i);
199             return 1;
200         }
201 
202         // If the next frame depends on this one, store it in cachedFrame. It
203         // is possible that we may discard a frame that future frames depend
204         // on, but the codec will simply redecode the discarded frame.
205         if ((static_cast<size_t>(i + 1) < frameInfos.size()) &&
206             (frameInfos[i + 1].fRequiredFrame == i)) {
207             cachedFrame = i;
208             memcpy(cachedFramePixels.reset(bmByteSize), bm.getPixels(), bmByteSize);
209         }
210 
211         int width = bm.width();
212         int height = bm.height();
213         write_nix_header(0x45AFC36E,  // "nïE" magic string as a u32le.
214                          width, height);
215         write_nie_pixels(width, height, bm);
216         if (result == SkCodec::kIncompleteInput) {
217             SkDebugf("Incomplete input.\n");
218             return 1;
219         }
220         if (firstFrameOnly) {
221             return 0;
222         }
223         write_nia_padding(width, height);
224     }
225     write_nia_footer(codec->getRepetitionCount(), stillImage);
226     return 0;
227 }
228