blob: 58f9a67360ff05051ccb4fdf98e0ec7a798c47e7 [file] [log] [blame]
Nigel Tao3ab9b1f2020-05-28 13:29:13 +10001/*
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
40static 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
47static 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
58static 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
67static 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
82static 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
104static 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
113static 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
126int 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}