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