xref: /aosp_15_r20/external/skia/tools/convert-to-nia.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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