xref: /aosp_15_r20/external/skia/src/codec/SkJpegxlCodec.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker  * Copyright 2021 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 #include "src/codec/SkJpegxlCodec.h"
9*c8dee2aaSAndroid Build Coastguard Worker 
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/codec/SkCodec.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/codec/SkJpegxlDecoder.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColorType.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkData.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkImageInfo.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkStream.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTypes.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/SkEncodedInfo.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTFitsIn.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTemplates.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkTo.h"
22*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skcms/skcms.h"
23*c8dee2aaSAndroid Build Coastguard Worker #include "src/codec/SkFrameHolder.h"
24*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkStreamPriv.h"
25*c8dee2aaSAndroid Build Coastguard Worker #include "src/core/SkSwizzlePriv.h"
26*c8dee2aaSAndroid Build Coastguard Worker 
27*c8dee2aaSAndroid Build Coastguard Worker #include "jxl/codestream_header.h"  // NO_G3_REWRITE
28*c8dee2aaSAndroid Build Coastguard Worker #include "jxl/decode.h"  // NO_G3_REWRITE
29*c8dee2aaSAndroid Build Coastguard Worker #include "jxl/decode_cxx.h"  // NO_G3_REWRITE
30*c8dee2aaSAndroid Build Coastguard Worker #include "jxl/types.h"  // NO_G3_REWRITE
31*c8dee2aaSAndroid Build Coastguard Worker 
32*c8dee2aaSAndroid Build Coastguard Worker #include <cstdint>
33*c8dee2aaSAndroid Build Coastguard Worker #include <cstring>
34*c8dee2aaSAndroid Build Coastguard Worker #include <limits>
35*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
36*c8dee2aaSAndroid Build Coastguard Worker #include <vector>
37*c8dee2aaSAndroid Build Coastguard Worker 
38*c8dee2aaSAndroid Build Coastguard Worker namespace {
39*c8dee2aaSAndroid Build Coastguard Worker 
40*c8dee2aaSAndroid Build Coastguard Worker class Frame : public SkFrame {
41*c8dee2aaSAndroid Build Coastguard Worker public:
Frame(int i,SkEncodedInfo::Alpha alpha)42*c8dee2aaSAndroid Build Coastguard Worker     explicit Frame(int i, SkEncodedInfo::Alpha alpha) : INHERITED(i), fReportedAlpha(alpha) {}
onReportedAlpha() const43*c8dee2aaSAndroid Build Coastguard Worker     SkEncodedInfo::Alpha onReportedAlpha() const override { return fReportedAlpha; }
44*c8dee2aaSAndroid Build Coastguard Worker 
45*c8dee2aaSAndroid Build Coastguard Worker private:
46*c8dee2aaSAndroid Build Coastguard Worker     const SkEncodedInfo::Alpha fReportedAlpha;
47*c8dee2aaSAndroid Build Coastguard Worker 
48*c8dee2aaSAndroid Build Coastguard Worker     using INHERITED = SkFrame;
49*c8dee2aaSAndroid Build Coastguard Worker };
50*c8dee2aaSAndroid Build Coastguard Worker 
51*c8dee2aaSAndroid Build Coastguard Worker }  // namespace
52*c8dee2aaSAndroid Build Coastguard Worker 
IsJpegxl(const void * buffer,size_t bytesRead)53*c8dee2aaSAndroid Build Coastguard Worker bool SkJpegxlCodec::IsJpegxl(const void* buffer, size_t bytesRead) {
54*c8dee2aaSAndroid Build Coastguard Worker     JxlSignature result = JxlSignatureCheck(reinterpret_cast<const uint8_t*>(buffer), bytesRead);
55*c8dee2aaSAndroid Build Coastguard Worker     return (result == JXL_SIG_CODESTREAM) || (result == JXL_SIG_CONTAINER);
56*c8dee2aaSAndroid Build Coastguard Worker }
57*c8dee2aaSAndroid Build Coastguard Worker 
58*c8dee2aaSAndroid Build Coastguard Worker class SkJpegxlCodecPriv : public SkFrameHolder {
59*c8dee2aaSAndroid Build Coastguard Worker public:
SkJpegxlCodecPriv()60*c8dee2aaSAndroid Build Coastguard Worker     SkJpegxlCodecPriv() : fDecoder(JxlDecoderMake(/* memory_manager= */ nullptr)) {}
61*c8dee2aaSAndroid Build Coastguard Worker     JxlDecoderPtr fDecoder;  // unique_ptr with custom destructor
62*c8dee2aaSAndroid Build Coastguard Worker     JxlBasicInfo fInfo;
63*c8dee2aaSAndroid Build Coastguard Worker     bool fSeenAllFrames = false;
64*c8dee2aaSAndroid Build Coastguard Worker     std::vector<Frame> fFrames;
65*c8dee2aaSAndroid Build Coastguard Worker     int fLastProcessedFrame = SkCodec::kNoFrame;
66*c8dee2aaSAndroid Build Coastguard Worker     void* fDst;
67*c8dee2aaSAndroid Build Coastguard Worker     size_t fPixelShift;
68*c8dee2aaSAndroid Build Coastguard Worker     size_t fRowBytes;
69*c8dee2aaSAndroid Build Coastguard Worker     SkColorType fDstColorType;
70*c8dee2aaSAndroid Build Coastguard Worker 
71*c8dee2aaSAndroid Build Coastguard Worker protected:
onGetFrame(int i) const72*c8dee2aaSAndroid Build Coastguard Worker     const SkFrame* onGetFrame(int i) const override {
73*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(i >= 0 && static_cast<size_t>(i) < fFrames.size());
74*c8dee2aaSAndroid Build Coastguard Worker         return static_cast<const SkFrame*>(&fFrames[i]);
75*c8dee2aaSAndroid Build Coastguard Worker     }
76*c8dee2aaSAndroid Build Coastguard Worker };
77*c8dee2aaSAndroid Build Coastguard Worker 
SkJpegxlCodec(std::unique_ptr<SkJpegxlCodecPriv> codec,SkEncodedInfo && info,std::unique_ptr<SkStream> stream,sk_sp<SkData> data)78*c8dee2aaSAndroid Build Coastguard Worker SkJpegxlCodec::SkJpegxlCodec(std::unique_ptr<SkJpegxlCodecPriv> codec,
79*c8dee2aaSAndroid Build Coastguard Worker                              SkEncodedInfo&& info,
80*c8dee2aaSAndroid Build Coastguard Worker                              std::unique_ptr<SkStream> stream,
81*c8dee2aaSAndroid Build Coastguard Worker                              sk_sp<SkData> data)
82*c8dee2aaSAndroid Build Coastguard Worker         : INHERITED(std::move(info), skcms_PixelFormat_RGBA_16161616LE, std::move(stream))
83*c8dee2aaSAndroid Build Coastguard Worker         , fCodec(std::move(codec))
84*c8dee2aaSAndroid Build Coastguard Worker         , fData(std::move(data)) {}
85*c8dee2aaSAndroid Build Coastguard Worker 
MakeFromStream(std::unique_ptr<SkStream> stream,Result * result)86*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkCodec> SkJpegxlCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
87*c8dee2aaSAndroid Build Coastguard Worker                                                        Result* result) {
88*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(result);
89*c8dee2aaSAndroid Build Coastguard Worker     if (!stream) {
90*c8dee2aaSAndroid Build Coastguard Worker         *result = SkCodec::kInvalidInput;
91*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
92*c8dee2aaSAndroid Build Coastguard Worker     }
93*c8dee2aaSAndroid Build Coastguard Worker     *result = kInternalError;
94*c8dee2aaSAndroid Build Coastguard Worker     // Either wrap or copy stream data.
95*c8dee2aaSAndroid Build Coastguard Worker     sk_sp<SkData> data = nullptr;
96*c8dee2aaSAndroid Build Coastguard Worker     if (stream->getMemoryBase()) {
97*c8dee2aaSAndroid Build Coastguard Worker         data = SkData::MakeWithoutCopy(stream->getMemoryBase(), stream->getLength());
98*c8dee2aaSAndroid Build Coastguard Worker     } else {
99*c8dee2aaSAndroid Build Coastguard Worker         data = SkCopyStreamToData(stream.get());
100*c8dee2aaSAndroid Build Coastguard Worker         // Data is copied; stream can be released now.
101*c8dee2aaSAndroid Build Coastguard Worker         stream.reset(nullptr);
102*c8dee2aaSAndroid Build Coastguard Worker     }
103*c8dee2aaSAndroid Build Coastguard Worker 
104*c8dee2aaSAndroid Build Coastguard Worker     auto priv = std::make_unique<SkJpegxlCodecPriv>();
105*c8dee2aaSAndroid Build Coastguard Worker     JxlDecoder* dec = priv->fDecoder.get();
106*c8dee2aaSAndroid Build Coastguard Worker 
107*c8dee2aaSAndroid Build Coastguard Worker     // Only query metadata this time.
108*c8dee2aaSAndroid Build Coastguard Worker     auto status = JxlDecoderSubscribeEvents(dec, JXL_DEC_BASIC_INFO | JXL_DEC_COLOR_ENCODING);
109*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
110*c8dee2aaSAndroid Build Coastguard Worker         // Fresh instance must accept request for subscription.
111*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
112*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
113*c8dee2aaSAndroid Build Coastguard Worker     }
114*c8dee2aaSAndroid Build Coastguard Worker 
115*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderSetInput(dec, data->bytes(), data->size());
116*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
117*c8dee2aaSAndroid Build Coastguard Worker         // Fresh instance must accept first chunk of input.
118*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
119*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
120*c8dee2aaSAndroid Build Coastguard Worker     }
121*c8dee2aaSAndroid Build Coastguard Worker 
122*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderProcessInput(dec);
123*c8dee2aaSAndroid Build Coastguard Worker     if (status == JXL_DEC_NEED_MORE_INPUT) {
124*c8dee2aaSAndroid Build Coastguard Worker         *result = kIncompleteInput;
125*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
126*c8dee2aaSAndroid Build Coastguard Worker     }
127*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_BASIC_INFO) {
128*c8dee2aaSAndroid Build Coastguard Worker         *result = kInvalidInput;
129*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
130*c8dee2aaSAndroid Build Coastguard Worker     }
131*c8dee2aaSAndroid Build Coastguard Worker     JxlBasicInfo& info = priv->fInfo;
132*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderGetBasicInfo(dec, &info);
133*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
134*c8dee2aaSAndroid Build Coastguard Worker         // Current event is "JXL_DEC_BASIC_INFO" -> can't fail.
135*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
136*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
137*c8dee2aaSAndroid Build Coastguard Worker     }
138*c8dee2aaSAndroid Build Coastguard Worker 
139*c8dee2aaSAndroid Build Coastguard Worker     // Check that image dimensions are not too large.
140*c8dee2aaSAndroid Build Coastguard Worker     if (!SkTFitsIn<int32_t>(info.xsize) || !SkTFitsIn<int32_t>(info.ysize)) {
141*c8dee2aaSAndroid Build Coastguard Worker         *result = kInvalidInput;
142*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
143*c8dee2aaSAndroid Build Coastguard Worker     }
144*c8dee2aaSAndroid Build Coastguard Worker     int32_t width = SkTo<int32_t>(info.xsize);
145*c8dee2aaSAndroid Build Coastguard Worker     int32_t height = SkTo<int32_t>(info.ysize);
146*c8dee2aaSAndroid Build Coastguard Worker 
147*c8dee2aaSAndroid Build Coastguard Worker     bool hasAlpha = (info.alpha_bits != 0);
148*c8dee2aaSAndroid Build Coastguard Worker     bool isGray = (info.num_color_channels == 1);
149*c8dee2aaSAndroid Build Coastguard Worker     SkEncodedInfo::Alpha alpha =
150*c8dee2aaSAndroid Build Coastguard Worker             hasAlpha ? SkEncodedInfo::kUnpremul_Alpha : SkEncodedInfo::kOpaque_Alpha;
151*c8dee2aaSAndroid Build Coastguard Worker     SkEncodedInfo::Color color;
152*c8dee2aaSAndroid Build Coastguard Worker     if (hasAlpha) {
153*c8dee2aaSAndroid Build Coastguard Worker         color = isGray ? SkEncodedInfo::kGrayAlpha_Color : SkEncodedInfo::kRGBA_Color;
154*c8dee2aaSAndroid Build Coastguard Worker     } else {
155*c8dee2aaSAndroid Build Coastguard Worker         color = isGray ? SkEncodedInfo::kGray_Color : SkEncodedInfo::kRGB_Color;
156*c8dee2aaSAndroid Build Coastguard Worker     }
157*c8dee2aaSAndroid Build Coastguard Worker 
158*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderProcessInput(dec);
159*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_COLOR_ENCODING) {
160*c8dee2aaSAndroid Build Coastguard Worker         *result = kInvalidInput;
161*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
162*c8dee2aaSAndroid Build Coastguard Worker     }
163*c8dee2aaSAndroid Build Coastguard Worker 
164*c8dee2aaSAndroid Build Coastguard Worker     size_t iccSize = 0;
165*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): format field is currently ignored by decoder.
166*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderGetICCProfileSize(
167*c8dee2aaSAndroid Build Coastguard Worker         dec, /* format = */ nullptr, JXL_COLOR_PROFILE_TARGET_DATA, &iccSize);
168*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
169*c8dee2aaSAndroid Build Coastguard Worker         // Likely incompatible colorspace.
170*c8dee2aaSAndroid Build Coastguard Worker         iccSize = 0;
171*c8dee2aaSAndroid Build Coastguard Worker     }
172*c8dee2aaSAndroid Build Coastguard Worker     std::unique_ptr<SkEncodedInfo::ICCProfile> profile = nullptr;
173*c8dee2aaSAndroid Build Coastguard Worker     if (iccSize) {
174*c8dee2aaSAndroid Build Coastguard Worker         auto icc = SkData::MakeUninitialized(iccSize);
175*c8dee2aaSAndroid Build Coastguard Worker         // TODO(eustas): format field is currently ignored by decoder.
176*c8dee2aaSAndroid Build Coastguard Worker         status = JxlDecoderGetColorAsICCProfile(dec,
177*c8dee2aaSAndroid Build Coastguard Worker                                                 /* format = */ nullptr,
178*c8dee2aaSAndroid Build Coastguard Worker                                                 JXL_COLOR_PROFILE_TARGET_DATA,
179*c8dee2aaSAndroid Build Coastguard Worker                                                 reinterpret_cast<uint8_t*>(icc->writable_data()),
180*c8dee2aaSAndroid Build Coastguard Worker                                                 iccSize);
181*c8dee2aaSAndroid Build Coastguard Worker         if (status != JXL_DEC_SUCCESS) {
182*c8dee2aaSAndroid Build Coastguard Worker             // Current event is JXL_DEC_COLOR_ENCODING -> can't fail.
183*c8dee2aaSAndroid Build Coastguard Worker             SkDEBUGFAIL("libjxl returned unexpected status");
184*c8dee2aaSAndroid Build Coastguard Worker             return nullptr;
185*c8dee2aaSAndroid Build Coastguard Worker         }
186*c8dee2aaSAndroid Build Coastguard Worker         profile = SkEncodedInfo::ICCProfile::Make(std::move(icc));
187*c8dee2aaSAndroid Build Coastguard Worker     }
188*c8dee2aaSAndroid Build Coastguard Worker 
189*c8dee2aaSAndroid Build Coastguard Worker     int bitsPerChannel = 16;
190*c8dee2aaSAndroid Build Coastguard Worker 
191*c8dee2aaSAndroid Build Coastguard Worker     *result = kSuccess;
192*c8dee2aaSAndroid Build Coastguard Worker     SkEncodedInfo encodedInfo =
193*c8dee2aaSAndroid Build Coastguard Worker             SkEncodedInfo::Make(width, height, color, alpha, bitsPerChannel, std::move(profile));
194*c8dee2aaSAndroid Build Coastguard Worker 
195*c8dee2aaSAndroid Build Coastguard Worker     return std::unique_ptr<SkCodec>(new SkJpegxlCodec(
196*c8dee2aaSAndroid Build Coastguard Worker             std::move(priv), std::move(encodedInfo), std::move(stream), std::move(data)));
197*c8dee2aaSAndroid Build Coastguard Worker }
198*c8dee2aaSAndroid Build Coastguard Worker 
onGetPixels(const SkImageInfo & dstInfo,void * dst,size_t rowBytes,const Options & options,int * rowsDecodedPtr)199*c8dee2aaSAndroid Build Coastguard Worker SkCodec::Result SkJpegxlCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
200*c8dee2aaSAndroid Build Coastguard Worker                                            const Options& options, int* rowsDecodedPtr) {
201*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): implement
202*c8dee2aaSAndroid Build Coastguard Worker     if (options.fSubset) {
203*c8dee2aaSAndroid Build Coastguard Worker         return kUnimplemented;
204*c8dee2aaSAndroid Build Coastguard Worker     }
205*c8dee2aaSAndroid Build Coastguard Worker     auto& codec = *fCodec.get();
206*c8dee2aaSAndroid Build Coastguard Worker     const int index = options.fFrameIndex;
207*c8dee2aaSAndroid Build Coastguard Worker     SkASSERT(0 == index || static_cast<size_t>(index) < codec.fFrames.size());
208*c8dee2aaSAndroid Build Coastguard Worker     auto* dec = codec.fDecoder.get();
209*c8dee2aaSAndroid Build Coastguard Worker     JxlDecoderStatus status;
210*c8dee2aaSAndroid Build Coastguard Worker 
211*c8dee2aaSAndroid Build Coastguard Worker     if ((codec.fLastProcessedFrame >= index) || (codec.fLastProcessedFrame = SkCodec::kNoFrame)) {
212*c8dee2aaSAndroid Build Coastguard Worker         codec.fLastProcessedFrame = SkCodec::kNoFrame;
213*c8dee2aaSAndroid Build Coastguard Worker         JxlDecoderRewind(dec);
214*c8dee2aaSAndroid Build Coastguard Worker         status = JxlDecoderSubscribeEvents(dec, JXL_DEC_FRAME | JXL_DEC_FULL_IMAGE);
215*c8dee2aaSAndroid Build Coastguard Worker         if (status != JXL_DEC_SUCCESS) {
216*c8dee2aaSAndroid Build Coastguard Worker             // Fresh decoder instance (after rewind) must accept subscription request.
217*c8dee2aaSAndroid Build Coastguard Worker             SkDEBUGFAIL("libjxl returned unexpected status");
218*c8dee2aaSAndroid Build Coastguard Worker             return kInternalError;
219*c8dee2aaSAndroid Build Coastguard Worker         }
220*c8dee2aaSAndroid Build Coastguard Worker         status = JxlDecoderSetInput(dec, fData->bytes(), fData->size());
221*c8dee2aaSAndroid Build Coastguard Worker         if (status != JXL_DEC_SUCCESS) {
222*c8dee2aaSAndroid Build Coastguard Worker             // Fresh decoder instance (after rewind) must accept first data chunk.
223*c8dee2aaSAndroid Build Coastguard Worker             SkDEBUGFAIL("libjxl returned unexpected status");
224*c8dee2aaSAndroid Build Coastguard Worker             return kInternalError;
225*c8dee2aaSAndroid Build Coastguard Worker         }
226*c8dee2aaSAndroid Build Coastguard Worker         SkASSERT(codec.fLastProcessedFrame + 1 == 0);
227*c8dee2aaSAndroid Build Coastguard Worker     }
228*c8dee2aaSAndroid Build Coastguard Worker 
229*c8dee2aaSAndroid Build Coastguard Worker     int nextFrame = codec.fLastProcessedFrame + 1;
230*c8dee2aaSAndroid Build Coastguard Worker     if (nextFrame < index) {
231*c8dee2aaSAndroid Build Coastguard Worker         JxlDecoderSkipFrames(dec, index - nextFrame);
232*c8dee2aaSAndroid Build Coastguard Worker     }
233*c8dee2aaSAndroid Build Coastguard Worker 
234*c8dee2aaSAndroid Build Coastguard Worker     // Decode till the frame start.
235*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderProcessInput(dec);
236*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): actually, frame is not completely processed; for streaming / partial decoding
237*c8dee2aaSAndroid Build Coastguard Worker     //               we should also add a flag that "last processed frame" is still incomplete, and
238*c8dee2aaSAndroid Build Coastguard Worker     //               flip that flag when frame decoding is over.
239*c8dee2aaSAndroid Build Coastguard Worker     codec.fLastProcessedFrame = index;
240*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_FRAME) {
241*c8dee2aaSAndroid Build Coastguard Worker         // TODO(eustas): check status: it might be either corrupted or incomplete input.
242*c8dee2aaSAndroid Build Coastguard Worker         return kInternalError;
243*c8dee2aaSAndroid Build Coastguard Worker     }
244*c8dee2aaSAndroid Build Coastguard Worker 
245*c8dee2aaSAndroid Build Coastguard Worker     codec.fDst = dst;
246*c8dee2aaSAndroid Build Coastguard Worker     codec.fRowBytes = rowBytes;
247*c8dee2aaSAndroid Build Coastguard Worker 
248*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): consider grayscale.
249*c8dee2aaSAndroid Build Coastguard Worker     uint32_t numColorChannels = 3;
250*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): consider no-alpha.
251*c8dee2aaSAndroid Build Coastguard Worker     uint32_t numAlphaChannels = 1;
252*c8dee2aaSAndroid Build Coastguard Worker     // NB: SKIA works with little-endian F16s.
253*c8dee2aaSAndroid Build Coastguard Worker     auto endianness = JXL_LITTLE_ENDIAN;
254*c8dee2aaSAndroid Build Coastguard Worker 
255*c8dee2aaSAndroid Build Coastguard Worker     // Internally JXL does most processing in floats. By "default" we request
256*c8dee2aaSAndroid Build Coastguard Worker     // output data type to be U8; it takes less memory, but results in some precision loss.
257*c8dee2aaSAndroid Build Coastguard Worker     //  We request F16 in two cases:
258*c8dee2aaSAndroid Build Coastguard Worker     //  - destination type is F16
259*c8dee2aaSAndroid Build Coastguard Worker     //  - color transformation is required; in this case values are remapped,
260*c8dee2aaSAndroid Build Coastguard Worker     //    and with 8-bit precision it is likely that visual artefact will appear
261*c8dee2aaSAndroid Build Coastguard Worker     //    (like banding, etc.)
262*c8dee2aaSAndroid Build Coastguard Worker     bool halfFloatOutput = false;
263*c8dee2aaSAndroid Build Coastguard Worker     if (fCodec->fDstColorType == kRGBA_F16_SkColorType) halfFloatOutput = true;
264*c8dee2aaSAndroid Build Coastguard Worker     if (colorXform()) halfFloatOutput = true;
265*c8dee2aaSAndroid Build Coastguard Worker     auto dataType = halfFloatOutput ? JXL_TYPE_FLOAT16 : JXL_TYPE_UINT8;
266*c8dee2aaSAndroid Build Coastguard Worker 
267*c8dee2aaSAndroid Build Coastguard Worker     JxlPixelFormat format =
268*c8dee2aaSAndroid Build Coastguard Worker         {numColorChannels + numAlphaChannels, dataType, endianness, /* align = */ 0};
269*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderSetImageOutCallback(dec, &format, SkJpegxlCodec::imageOutCallback, this);
270*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
271*c8dee2aaSAndroid Build Coastguard Worker         // Current event is JXL_DEC_FRAME -> decoder must accept callback.
272*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
273*c8dee2aaSAndroid Build Coastguard Worker         return kInternalError;
274*c8dee2aaSAndroid Build Coastguard Worker     }
275*c8dee2aaSAndroid Build Coastguard Worker 
276*c8dee2aaSAndroid Build Coastguard Worker     // Decode till the frame start.
277*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderProcessInput(dec);
278*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_FULL_IMAGE) {
279*c8dee2aaSAndroid Build Coastguard Worker         // TODO(eustas): check status: it might be either corrupted or incomplete input.
280*c8dee2aaSAndroid Build Coastguard Worker         return kInternalError;
281*c8dee2aaSAndroid Build Coastguard Worker     }
282*c8dee2aaSAndroid Build Coastguard Worker     // TODO(eustas): currently it is supposed that complete input is accessible;
283*c8dee2aaSAndroid Build Coastguard Worker     //               when streaming support is added JXL_DEC_NEED_MORE_INPUT would also
284*c8dee2aaSAndroid Build Coastguard Worker     //               become a legal outcome; amount of decoded scanlines should be calculated
285*c8dee2aaSAndroid Build Coastguard Worker     //               based on callback invocations / render-pipeline API.
286*c8dee2aaSAndroid Build Coastguard Worker     *rowsDecodedPtr = dstInfo.height();
287*c8dee2aaSAndroid Build Coastguard Worker 
288*c8dee2aaSAndroid Build Coastguard Worker     return kSuccess;
289*c8dee2aaSAndroid Build Coastguard Worker }
290*c8dee2aaSAndroid Build Coastguard Worker 
onRewind()291*c8dee2aaSAndroid Build Coastguard Worker bool SkJpegxlCodec::onRewind() {
292*c8dee2aaSAndroid Build Coastguard Worker     JxlDecoderRewind(fCodec->fDecoder.get());
293*c8dee2aaSAndroid Build Coastguard Worker     return true;
294*c8dee2aaSAndroid Build Coastguard Worker }
295*c8dee2aaSAndroid Build Coastguard Worker 
conversionSupported(const SkImageInfo & dstInfo,bool srcIsOpaque,bool needsColorXform)296*c8dee2aaSAndroid Build Coastguard Worker bool SkJpegxlCodec::conversionSupported(const SkImageInfo& dstInfo, bool srcIsOpaque,
297*c8dee2aaSAndroid Build Coastguard Worker                                         bool needsColorXform) {
298*c8dee2aaSAndroid Build Coastguard Worker     fCodec->fDstColorType = dstInfo.colorType();
299*c8dee2aaSAndroid Build Coastguard Worker     switch (dstInfo.colorType()) {
300*c8dee2aaSAndroid Build Coastguard Worker         case kRGBA_8888_SkColorType:
301*c8dee2aaSAndroid Build Coastguard Worker             return true;  // memcpy
302*c8dee2aaSAndroid Build Coastguard Worker         case kBGRA_8888_SkColorType:
303*c8dee2aaSAndroid Build Coastguard Worker             return true;  // rgba->bgra
304*c8dee2aaSAndroid Build Coastguard Worker 
305*c8dee2aaSAndroid Build Coastguard Worker         case kRGBA_F16_SkColorType:
306*c8dee2aaSAndroid Build Coastguard Worker             SkASSERT(needsColorXform);  // TODO(eustas): not necessary for JXL.
307*c8dee2aaSAndroid Build Coastguard Worker             return true;  // memcpy
308*c8dee2aaSAndroid Build Coastguard Worker 
309*c8dee2aaSAndroid Build Coastguard Worker         // TODO(eustas): implement
310*c8dee2aaSAndroid Build Coastguard Worker         case kRGB_565_SkColorType:
311*c8dee2aaSAndroid Build Coastguard Worker             return false;
312*c8dee2aaSAndroid Build Coastguard Worker         case kGray_8_SkColorType:
313*c8dee2aaSAndroid Build Coastguard Worker             return false;
314*c8dee2aaSAndroid Build Coastguard Worker         case kAlpha_8_SkColorType:
315*c8dee2aaSAndroid Build Coastguard Worker             return false;
316*c8dee2aaSAndroid Build Coastguard Worker 
317*c8dee2aaSAndroid Build Coastguard Worker         default:
318*c8dee2aaSAndroid Build Coastguard Worker             return false;
319*c8dee2aaSAndroid Build Coastguard Worker     }
320*c8dee2aaSAndroid Build Coastguard Worker     return true;
321*c8dee2aaSAndroid Build Coastguard Worker }
322*c8dee2aaSAndroid Build Coastguard Worker 
imageOutCallback(void * opaque,size_t x,size_t y,size_t num_pixels,const void * pixels)323*c8dee2aaSAndroid Build Coastguard Worker void SkJpegxlCodec::imageOutCallback(void* opaque, size_t x, size_t y,
324*c8dee2aaSAndroid Build Coastguard Worker                                      size_t num_pixels, const void* pixels) {
325*c8dee2aaSAndroid Build Coastguard Worker     SkJpegxlCodec* instance = reinterpret_cast<SkJpegxlCodec*>(opaque);
326*c8dee2aaSAndroid Build Coastguard Worker     auto& codec = *instance->fCodec.get();
327*c8dee2aaSAndroid Build Coastguard Worker     size_t offset = y * codec.fRowBytes + (x << codec.fPixelShift);
328*c8dee2aaSAndroid Build Coastguard Worker     void* dst = SkTAddOffset<void>(codec.fDst, offset);
329*c8dee2aaSAndroid Build Coastguard Worker     if (instance->colorXform()) {
330*c8dee2aaSAndroid Build Coastguard Worker         instance->applyColorXform(dst, pixels, num_pixels);
331*c8dee2aaSAndroid Build Coastguard Worker         return;
332*c8dee2aaSAndroid Build Coastguard Worker     }
333*c8dee2aaSAndroid Build Coastguard Worker     switch (codec.fDstColorType) {
334*c8dee2aaSAndroid Build Coastguard Worker         case kRGBA_8888_SkColorType:
335*c8dee2aaSAndroid Build Coastguard Worker             memcpy(dst, pixels, 4 * num_pixels);
336*c8dee2aaSAndroid Build Coastguard Worker             return;
337*c8dee2aaSAndroid Build Coastguard Worker         case kBGRA_8888_SkColorType:
338*c8dee2aaSAndroid Build Coastguard Worker             SkOpts::RGBA_to_bgrA((uint32_t*) dst, (const uint32_t*)(pixels), num_pixels);
339*c8dee2aaSAndroid Build Coastguard Worker             return;
340*c8dee2aaSAndroid Build Coastguard Worker         case kRGBA_F16_SkColorType:
341*c8dee2aaSAndroid Build Coastguard Worker             memcpy(dst, pixels, 8 * num_pixels);
342*c8dee2aaSAndroid Build Coastguard Worker             return;
343*c8dee2aaSAndroid Build Coastguard Worker         default:
344*c8dee2aaSAndroid Build Coastguard Worker             SK_ABORT("Selected output format is not supported yet");
345*c8dee2aaSAndroid Build Coastguard Worker             return;
346*c8dee2aaSAndroid Build Coastguard Worker     }
347*c8dee2aaSAndroid Build Coastguard Worker }
348*c8dee2aaSAndroid Build Coastguard Worker 
scanFrames()349*c8dee2aaSAndroid Build Coastguard Worker bool SkJpegxlCodec::scanFrames() {
350*c8dee2aaSAndroid Build Coastguard Worker     auto decoder = JxlDecoderMake(/* memory_manager = */ nullptr);
351*c8dee2aaSAndroid Build Coastguard Worker     JxlDecoder* dec = decoder.get();
352*c8dee2aaSAndroid Build Coastguard Worker     auto* frameHolder = fCodec.get();
353*c8dee2aaSAndroid Build Coastguard Worker     auto& frames = frameHolder->fFrames;
354*c8dee2aaSAndroid Build Coastguard Worker     const auto& info = fCodec->fInfo;
355*c8dee2aaSAndroid Build Coastguard Worker     frames.clear();
356*c8dee2aaSAndroid Build Coastguard Worker 
357*c8dee2aaSAndroid Build Coastguard Worker     auto alpha = (info.alpha_bits != 0) ? SkEncodedInfo::Alpha::kUnpremul_Alpha
358*c8dee2aaSAndroid Build Coastguard Worker                                         : SkEncodedInfo::Alpha::kOpaque_Alpha;
359*c8dee2aaSAndroid Build Coastguard Worker 
360*c8dee2aaSAndroid Build Coastguard Worker     auto status = JxlDecoderSubscribeEvents(dec, JXL_DEC_FRAME);
361*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
362*c8dee2aaSAndroid Build Coastguard Worker         // Fresh instance must accept request for subscription.
363*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
364*c8dee2aaSAndroid Build Coastguard Worker         return true;
365*c8dee2aaSAndroid Build Coastguard Worker     }
366*c8dee2aaSAndroid Build Coastguard Worker 
367*c8dee2aaSAndroid Build Coastguard Worker     status = JxlDecoderSetInput(dec, fData->bytes(), fData->size());
368*c8dee2aaSAndroid Build Coastguard Worker     if (status != JXL_DEC_SUCCESS) {
369*c8dee2aaSAndroid Build Coastguard Worker         // Fresh instance must accept first input chunk.
370*c8dee2aaSAndroid Build Coastguard Worker         SkDEBUGFAIL("libjxl returned unexpected status");
371*c8dee2aaSAndroid Build Coastguard Worker         return true;
372*c8dee2aaSAndroid Build Coastguard Worker     }
373*c8dee2aaSAndroid Build Coastguard Worker 
374*c8dee2aaSAndroid Build Coastguard Worker     while (true) {
375*c8dee2aaSAndroid Build Coastguard Worker         status = JxlDecoderProcessInput(dec);
376*c8dee2aaSAndroid Build Coastguard Worker         switch (status) {
377*c8dee2aaSAndroid Build Coastguard Worker             case JXL_DEC_FRAME: {
378*c8dee2aaSAndroid Build Coastguard Worker                 size_t frameId = frames.size();
379*c8dee2aaSAndroid Build Coastguard Worker                 JxlFrameHeader frameHeader;
380*c8dee2aaSAndroid Build Coastguard Worker                 if (JxlDecoderGetFrameHeader(dec, &frameHeader) != JXL_DEC_SUCCESS) {
381*c8dee2aaSAndroid Build Coastguard Worker                     return true;
382*c8dee2aaSAndroid Build Coastguard Worker                 }
383*c8dee2aaSAndroid Build Coastguard Worker                 frames.emplace_back(static_cast<int>(frameId), alpha);
384*c8dee2aaSAndroid Build Coastguard Worker                 auto& frame = frames.back();
385*c8dee2aaSAndroid Build Coastguard Worker                 // TODO(eustas): for better consistency we need to track total duration and report
386*c8dee2aaSAndroid Build Coastguard Worker                 //               frame duration as delta to previous frame.
387*c8dee2aaSAndroid Build Coastguard Worker                 int duration = (1000 * frameHeader.duration * info.animation.tps_denominator) /
388*c8dee2aaSAndroid Build Coastguard Worker                                info.animation.tps_numerator;
389*c8dee2aaSAndroid Build Coastguard Worker                 frame.setDuration(duration);
390*c8dee2aaSAndroid Build Coastguard Worker                 frameHolder->setAlphaAndRequiredFrame(&frame);
391*c8dee2aaSAndroid Build Coastguard Worker                 break;
392*c8dee2aaSAndroid Build Coastguard Worker             }
393*c8dee2aaSAndroid Build Coastguard Worker             case JXL_DEC_SUCCESS: {
394*c8dee2aaSAndroid Build Coastguard Worker                 return true;
395*c8dee2aaSAndroid Build Coastguard Worker             }
396*c8dee2aaSAndroid Build Coastguard Worker             default: {
397*c8dee2aaSAndroid Build Coastguard Worker                 return false;
398*c8dee2aaSAndroid Build Coastguard Worker             }
399*c8dee2aaSAndroid Build Coastguard Worker         }
400*c8dee2aaSAndroid Build Coastguard Worker     }
401*c8dee2aaSAndroid Build Coastguard Worker }
402*c8dee2aaSAndroid Build Coastguard Worker 
onGetFrameCount()403*c8dee2aaSAndroid Build Coastguard Worker int SkJpegxlCodec::onGetFrameCount() {
404*c8dee2aaSAndroid Build Coastguard Worker     if (!fCodec->fInfo.have_animation) {
405*c8dee2aaSAndroid Build Coastguard Worker         return 1;
406*c8dee2aaSAndroid Build Coastguard Worker     }
407*c8dee2aaSAndroid Build Coastguard Worker 
408*c8dee2aaSAndroid Build Coastguard Worker     if (!fCodec->fSeenAllFrames) {
409*c8dee2aaSAndroid Build Coastguard Worker         fCodec->fSeenAllFrames = scanFrames();
410*c8dee2aaSAndroid Build Coastguard Worker     }
411*c8dee2aaSAndroid Build Coastguard Worker 
412*c8dee2aaSAndroid Build Coastguard Worker     return fCodec->fFrames.size();
413*c8dee2aaSAndroid Build Coastguard Worker }
414*c8dee2aaSAndroid Build Coastguard Worker 
onGetFrameInfo(int index,FrameInfo * frameInfo) const415*c8dee2aaSAndroid Build Coastguard Worker bool SkJpegxlCodec::onGetFrameInfo(int index, FrameInfo* frameInfo) const {
416*c8dee2aaSAndroid Build Coastguard Worker     if (index < 0) {
417*c8dee2aaSAndroid Build Coastguard Worker         return false;
418*c8dee2aaSAndroid Build Coastguard Worker     }
419*c8dee2aaSAndroid Build Coastguard Worker     if (static_cast<size_t>(index) >= fCodec->fFrames.size()) {
420*c8dee2aaSAndroid Build Coastguard Worker         return false;
421*c8dee2aaSAndroid Build Coastguard Worker     }
422*c8dee2aaSAndroid Build Coastguard Worker     fCodec->fFrames[index].fillIn(frameInfo, true);
423*c8dee2aaSAndroid Build Coastguard Worker     return true;
424*c8dee2aaSAndroid Build Coastguard Worker }
425*c8dee2aaSAndroid Build Coastguard Worker 
onGetRepetitionCount()426*c8dee2aaSAndroid Build Coastguard Worker int SkJpegxlCodec::onGetRepetitionCount() {
427*c8dee2aaSAndroid Build Coastguard Worker     JxlBasicInfo& info = fCodec->fInfo;
428*c8dee2aaSAndroid Build Coastguard Worker     if (!info.have_animation) {
429*c8dee2aaSAndroid Build Coastguard Worker         return 0;
430*c8dee2aaSAndroid Build Coastguard Worker     }
431*c8dee2aaSAndroid Build Coastguard Worker 
432*c8dee2aaSAndroid Build Coastguard Worker     if (info.animation.num_loops == 0) {
433*c8dee2aaSAndroid Build Coastguard Worker         return kRepetitionCountInfinite;
434*c8dee2aaSAndroid Build Coastguard Worker     }
435*c8dee2aaSAndroid Build Coastguard Worker 
436*c8dee2aaSAndroid Build Coastguard Worker     if (SkTFitsIn<int>(info.animation.num_loops)) {
437*c8dee2aaSAndroid Build Coastguard Worker         return info.animation.num_loops - 1;
438*c8dee2aaSAndroid Build Coastguard Worker     }
439*c8dee2aaSAndroid Build Coastguard Worker 
440*c8dee2aaSAndroid Build Coastguard Worker     // Largest "non-infinite" value.
441*c8dee2aaSAndroid Build Coastguard Worker     return std::numeric_limits<int>::max();
442*c8dee2aaSAndroid Build Coastguard Worker }
443*c8dee2aaSAndroid Build Coastguard Worker 
getFrameHolder() const444*c8dee2aaSAndroid Build Coastguard Worker const SkFrameHolder* SkJpegxlCodec::getFrameHolder() const {
445*c8dee2aaSAndroid Build Coastguard Worker     return fCodec.get();
446*c8dee2aaSAndroid Build Coastguard Worker }
447*c8dee2aaSAndroid Build Coastguard Worker 
448*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
449*c8dee2aaSAndroid Build Coastguard Worker // SkCodec::Result SkJpegxlCodec::onStartScanlineDecode(
450*c8dee2aaSAndroid Build Coastguard Worker //     const SkImageInfo& /*dstInfo*/, const Options& /*options*/) { return kUnimplemented; }
451*c8dee2aaSAndroid Build Coastguard Worker 
452*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
453*c8dee2aaSAndroid Build Coastguard Worker // SkCodec::Result SkJpegxlCodec::onStartIncrementalDecode(
454*c8dee2aaSAndroid Build Coastguard Worker //     const SkImageInfo& /*dstInfo*/, void*, size_t, const Options&) { return kUnimplemented; }
455*c8dee2aaSAndroid Build Coastguard Worker 
456*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
457*c8dee2aaSAndroid Build Coastguard Worker // SkCodec::Result SkJpegxlCodec::onIncrementalDecode(int*) { return kUnimplemented; }
458*c8dee2aaSAndroid Build Coastguard Worker 
459*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
460*c8dee2aaSAndroid Build Coastguard Worker // bool SkJpegxlCodec::onSkipScanlines(int /*countLines*/) { return false; }
461*c8dee2aaSAndroid Build Coastguard Worker 
462*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
463*c8dee2aaSAndroid Build Coastguard Worker // int SkJpegxlCodec::onGetScanlines(
464*c8dee2aaSAndroid Build Coastguard Worker //     void* /*dst*/, int /*countLines*/, size_t /*rowBytes*/) { return 0; }
465*c8dee2aaSAndroid Build Coastguard Worker 
466*c8dee2aaSAndroid Build Coastguard Worker // TODO(eustas): implement
467*c8dee2aaSAndroid Build Coastguard Worker // SkSampler* SkJpegxlCodec::getSampler(bool /*createIfNecessary*/) { return nullptr; }
468*c8dee2aaSAndroid Build Coastguard Worker 
469*c8dee2aaSAndroid Build Coastguard Worker namespace SkJpegxlDecoder {
IsJpegxl(const void * data,size_t len)470*c8dee2aaSAndroid Build Coastguard Worker bool IsJpegxl(const void* data, size_t len) {
471*c8dee2aaSAndroid Build Coastguard Worker     return SkJpegxlCodec::IsJpegxl(data, len);
472*c8dee2aaSAndroid Build Coastguard Worker }
473*c8dee2aaSAndroid Build Coastguard Worker 
Decode(std::unique_ptr<SkStream> stream,SkCodec::Result * outResult,SkCodecs::DecodeContext)474*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkCodec> Decode(std::unique_ptr<SkStream> stream,
475*c8dee2aaSAndroid Build Coastguard Worker                                 SkCodec::Result* outResult,
476*c8dee2aaSAndroid Build Coastguard Worker                                 SkCodecs::DecodeContext) {
477*c8dee2aaSAndroid Build Coastguard Worker     SkCodec::Result resultStorage;
478*c8dee2aaSAndroid Build Coastguard Worker     if (!outResult) {
479*c8dee2aaSAndroid Build Coastguard Worker         outResult = &resultStorage;
480*c8dee2aaSAndroid Build Coastguard Worker     }
481*c8dee2aaSAndroid Build Coastguard Worker     return SkJpegxlCodec::MakeFromStream(std::move(stream), outResult);
482*c8dee2aaSAndroid Build Coastguard Worker }
483*c8dee2aaSAndroid Build Coastguard Worker 
Decode(sk_sp<SkData> data,SkCodec::Result * outResult,SkCodecs::DecodeContext)484*c8dee2aaSAndroid Build Coastguard Worker std::unique_ptr<SkCodec> Decode(sk_sp<SkData> data,
485*c8dee2aaSAndroid Build Coastguard Worker                                 SkCodec::Result* outResult,
486*c8dee2aaSAndroid Build Coastguard Worker                                 SkCodecs::DecodeContext) {
487*c8dee2aaSAndroid Build Coastguard Worker     if (!data) {
488*c8dee2aaSAndroid Build Coastguard Worker         if (outResult) {
489*c8dee2aaSAndroid Build Coastguard Worker             *outResult = SkCodec::kInvalidInput;
490*c8dee2aaSAndroid Build Coastguard Worker         }
491*c8dee2aaSAndroid Build Coastguard Worker         return nullptr;
492*c8dee2aaSAndroid Build Coastguard Worker     }
493*c8dee2aaSAndroid Build Coastguard Worker     return Decode(SkMemoryStream::Make(std::move(data)), outResult, nullptr);
494*c8dee2aaSAndroid Build Coastguard Worker }
495*c8dee2aaSAndroid Build Coastguard Worker }  // namespace SkJpegDecoder
496