Skip to content

Commit b28b7b0

Browse files
committed
Bug 1707590 - Part 2: Implement nsJXLDecoder r=tnikkel
Differential Revision: https://phabricator.services.mozilla.com/D113359
1 parent 4cf43df commit b28b7b0

26 files changed

+325
-5
lines changed

image/DecoderFactory.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
#ifdef MOZ_AV1
2424
# include "nsAVIFDecoder.h"
2525
#endif
26+
#ifdef MOZ_JXL
27+
# include "nsJXLDecoder.h"
28+
#endif
2629

2730
namespace mozilla {
2831

@@ -88,6 +91,11 @@ DecoderType DecoderFactory::GetDecoderType(const char* aMimeType) {
8891
type = DecoderType::AVIF;
8992
}
9093
#endif
94+
#ifdef MOZ_JXL
95+
else if (!strcmp(aMimeType, IMAGE_JXL) && StaticPrefs::image_jxl_enabled()) {
96+
type = DecoderType::JXL;
97+
}
98+
#endif
9199

92100
return type;
93101
}
@@ -130,6 +138,11 @@ already_AddRefed<Decoder> DecoderFactory::GetDecoder(DecoderType aType,
130138
case DecoderType::AVIF:
131139
decoder = new nsAVIFDecoder(aImage);
132140
break;
141+
#endif
142+
#ifdef MOZ_JXL
143+
case DecoderType::JXL:
144+
decoder = new nsJXLDecoder(aImage);
145+
break;
133146
#endif
134147
default:
135148
MOZ_ASSERT_UNREACHABLE("Unknown decoder type");

image/DecoderFactory.h

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@
1515
#include "nsCOMPtr.h"
1616
#include "SurfaceFlags.h"
1717

18-
namespace mozilla {
19-
namespace image {
18+
namespace mozilla::image {
2019

2120
class Decoder;
2221
class IDecodingTask;
@@ -39,6 +38,7 @@ enum class DecoderType {
3938
ICON,
4039
WEBP,
4140
AVIF,
41+
JXL,
4242
UNKNOWN
4343
};
4444

@@ -201,7 +201,6 @@ class DecoderFactory {
201201
bool aIsRedecode);
202202
};
203203

204-
} // namespace image
205-
} // namespace mozilla
204+
} // namespace mozilla::image
206205

207206
#endif // mozilla_image_DecoderFactory_h

image/build/nsImageModule.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,10 +58,14 @@ nsresult mozilla::image::EnsureModuleInitialized() {
5858

5959
static ImageEnablementCookie kAVIFCookie = {
6060
mozilla::StaticPrefs::image_avif_enabled, "image/avif"_ns};
61+
static ImageEnablementCookie kJXLCookie = {
62+
mozilla::StaticPrefs::image_jxl_enabled, "image/jxl"_ns};
6163
static ImageEnablementCookie kWebPCookie = {
6264
mozilla::StaticPrefs::image_webp_enabled, "image/webp"_ns};
6365
Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration,
6466
"image.avif.enabled", &kAVIFCookie);
67+
Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration,
68+
"image.jxl.enabled", &kJXLCookie);
6569
Preferences::RegisterCallbackAndCall(UpdateContentViewerRegistration,
6670
"image.webp.enabled", &kWebPCookie);
6771

image/decoders/moz.build

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ if CONFIG["MOZ_AV1"]:
3636
"nsAVIFDecoder.cpp",
3737
]
3838

39+
if CONFIG["MOZ_JXL"]:
40+
UNIFIED_SOURCES += [
41+
"nsJXLDecoder.cpp",
42+
]
43+
3944
include("/ipc/chromium/chromium-config.mozbuild")
4045

4146
LOCAL_INCLUDES += [

image/decoders/nsJXLDecoder.cpp

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
1+
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2+
*
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#include "ImageLogging.h" // Must appear first
8+
#include "gfxPlatform.h"
9+
#include "jxl/codestream_header.h"
10+
#include "jxl/decode_cxx.h"
11+
#include "jxl/types.h"
12+
#include "mozilla/TelemetryHistogramEnums.h"
13+
#include "mozilla/gfx/Point.h"
14+
#include "nsJXLDecoder.h"
15+
16+
#include "RasterImage.h"
17+
#include "SurfacePipeFactory.h"
18+
19+
using namespace mozilla::gfx;
20+
21+
namespace mozilla::image {
22+
23+
#define JXL_TRY(expr) \
24+
do { \
25+
JxlDecoderStatus status = (expr); \
26+
if (status != JXL_DEC_SUCCESS) { \
27+
return Transition::TerminateFailure(); \
28+
} \
29+
} while (0);
30+
31+
#define JXL_TRY_BOOL(expr) \
32+
do { \
33+
bool succeeded = (expr); \
34+
if (!succeeded) { \
35+
return Transition::TerminateFailure(); \
36+
} \
37+
} while (0);
38+
39+
static LazyLogModule sJXLLog("JXLDecoder");
40+
41+
nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
42+
: Decoder(aImage),
43+
mLexer(Transition::ToUnbuffered(State::FINISHED_JXL_DATA, State::JXL_DATA,
44+
SIZE_MAX),
45+
Transition::TerminateSuccess()),
46+
mDecoder(JxlDecoderMake(nullptr)),
47+
mParallelRunner(
48+
JxlThreadParallelRunnerMake(nullptr, PreferredThreadCount())) {
49+
JxlDecoderSubscribeEvents(mDecoder.get(),
50+
JXL_DEC_BASIC_INFO | JXL_DEC_FULL_IMAGE);
51+
JxlDecoderSetParallelRunner(mDecoder.get(), JxlThreadParallelRunner,
52+
mParallelRunner.get());
53+
54+
MOZ_LOG(sJXLLog, LogLevel::Debug,
55+
("[this=%p] nsJXLDecoder::nsJXLDecoder", this));
56+
}
57+
58+
nsJXLDecoder::~nsJXLDecoder() {
59+
MOZ_LOG(sJXLLog, LogLevel::Debug,
60+
("[this=%p] nsJXLDecoder::~nsJXLDecoder", this));
61+
}
62+
63+
size_t nsJXLDecoder::PreferredThreadCount() {
64+
if (IsMetadataDecode()) {
65+
return 0; // no additional worker thread
66+
}
67+
return JxlThreadParallelRunnerDefaultNumWorkerThreads();
68+
}
69+
70+
LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
71+
IResumable* aOnResume) {
72+
// return LexerResult(TerminalState::FAILURE);
73+
MOZ_ASSERT(!HasError(), "Shouldn't call DoDecode after error!");
74+
75+
return mLexer.Lex(aIterator, aOnResume,
76+
[=](State aState, const char* aData, size_t aLength) {
77+
switch (aState) {
78+
case State::JXL_DATA:
79+
return ReadJXLData(aData, aLength);
80+
case State::FINISHED_JXL_DATA:
81+
return FinishedJXLData();
82+
}
83+
MOZ_CRASH("Unknown State");
84+
});
85+
};
86+
87+
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData(
88+
const char* aData, size_t aLength) {
89+
const uint8_t* input = (const uint8_t*)aData;
90+
size_t length = aLength;
91+
if (mBuffer.length() != 0) {
92+
JXL_TRY_BOOL(mBuffer.append(aData, aLength));
93+
input = mBuffer.begin();
94+
length = mBuffer.length();
95+
}
96+
JXL_TRY(JxlDecoderSetInput(mDecoder.get(), input, length));
97+
98+
while (true) {
99+
JxlDecoderStatus status = JxlDecoderProcessInput(mDecoder.get());
100+
switch (status) {
101+
case JXL_DEC_ERROR:
102+
default:
103+
return Transition::TerminateFailure();
104+
105+
case JXL_DEC_NEED_MORE_INPUT: {
106+
size_t remaining = JxlDecoderReleaseInput(mDecoder.get());
107+
mBuffer.clear();
108+
JXL_TRY_BOOL(mBuffer.append(aData + aLength - remaining, remaining));
109+
return Transition::ContinueUnbuffered(State::JXL_DATA);
110+
}
111+
112+
case JXL_DEC_BASIC_INFO: {
113+
JXL_TRY(JxlDecoderGetBasicInfo(mDecoder.get(), &mInfo));
114+
PostSize(mInfo.xsize, mInfo.ysize);
115+
if (mInfo.alpha_bits > 0) {
116+
PostHasTransparency();
117+
}
118+
if (IsMetadataDecode()) {
119+
return Transition::TerminateSuccess();
120+
}
121+
break;
122+
}
123+
124+
case JXL_DEC_NEED_IMAGE_OUT_BUFFER: {
125+
size_t size = 0;
126+
JxlPixelFormat format{4, JXL_TYPE_UINT8, JXL_LITTLE_ENDIAN, 0};
127+
JXL_TRY(JxlDecoderImageOutBufferSize(mDecoder.get(), &format, &size));
128+
129+
mOutBuffer.clear();
130+
JXL_TRY_BOOL(mOutBuffer.growBy(size));
131+
JXL_TRY(JxlDecoderSetImageOutBuffer(mDecoder.get(), &format,
132+
mOutBuffer.begin(), size));
133+
break;
134+
}
135+
136+
case JXL_DEC_FULL_IMAGE: {
137+
gfx::IntSize size(mInfo.xsize, mInfo.ysize);
138+
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
139+
this, size, OutputSize(), FullFrame(), SurfaceFormat::R8G8B8A8,
140+
SurfaceFormat::OS_RGBA, Nothing(), nullptr, SurfacePipeFlags());
141+
for (uint8_t* rowPtr = mOutBuffer.begin(); rowPtr < mOutBuffer.end();
142+
rowPtr += mInfo.xsize * 4) {
143+
pipe->WriteBuffer(reinterpret_cast<uint32_t*>(rowPtr));
144+
}
145+
146+
if (Maybe<SurfaceInvalidRect> invalidRect = pipe->TakeInvalidRect()) {
147+
PostInvalidation(invalidRect->mInputSpaceRect,
148+
Some(invalidRect->mOutputSpaceRect));
149+
}
150+
PostFrameStop();
151+
PostDecodeDone();
152+
return Transition::TerminateSuccess();
153+
}
154+
}
155+
}
156+
}
157+
158+
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::FinishedJXLData() {
159+
MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
160+
return Transition::TerminateFailure();
161+
}
162+
163+
} // namespace mozilla::image

image/decoders/nsJXLDecoder.h

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2+
*
3+
* This Source Code Form is subject to the terms of the Mozilla Public
4+
* License, v. 2.0. If a copy of the MPL was not distributed with this
5+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6+
7+
#ifndef mozilla_image_decoders_nsJXLDecoder_h
8+
#define mozilla_image_decoders_nsJXLDecoder_h
9+
10+
#include "Decoder.h"
11+
#include "mp4parse.h"
12+
#include "SurfacePipe.h"
13+
14+
#include "jxl/decode_cxx.h"
15+
#include "jxl/thread_parallel_runner_cxx.h"
16+
17+
#include "mozilla/Telemetry.h"
18+
19+
namespace mozilla::image {
20+
class RasterImage;
21+
22+
class nsJXLDecoder final : public Decoder {
23+
public:
24+
virtual ~nsJXLDecoder();
25+
26+
DecoderType GetType() const override { return DecoderType::JXL; }
27+
28+
protected:
29+
LexerResult DoDecode(SourceBufferIterator& aIterator,
30+
IResumable* aOnResume) override;
31+
32+
private:
33+
friend class DecoderFactory;
34+
35+
// Decoders should only be instantiated via DecoderFactory.
36+
explicit nsJXLDecoder(RasterImage* aImage);
37+
38+
size_t PreferredThreadCount();
39+
40+
enum class State { JXL_DATA, FINISHED_JXL_DATA };
41+
42+
LexerTransition<State> ReadJXLData(const char* aData, size_t aLength);
43+
LexerTransition<State> FinishedJXLData();
44+
45+
StreamingLexer<State> mLexer;
46+
JxlDecoderPtr mDecoder;
47+
JxlThreadParallelRunnerPtr mParallelRunner;
48+
Vector<uint8_t> mBuffer;
49+
Vector<uint8_t> mOutBuffer;
50+
JxlBasicInfo mInfo{};
51+
};
52+
53+
} // namespace mozilla::image
54+
55+
#endif // mozilla_image_decoders_nsJXLDecoder_h

image/imgLoader.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2766,6 +2766,11 @@ nsresult imgLoader::GetMimeTypeFromContent(const char* aContents,
27662766
detected) &&
27672767
detected.Equals(IMAGE_AVIF)) {
27682768
aContentType.AssignLiteral(IMAGE_AVIF);
2769+
} else if ((aLength >= 2 && !memcmp(aContents, "\xFF\x0A", 2)) ||
2770+
(aLength >= 12 &&
2771+
!memcmp(aContents, "\x00\x00\x00\x0CJXL \x0D\x0A\x87\x0A", 12))) {
2772+
// Each version is for containerless and containerful files respectively.
2773+
aContentType.AssignLiteral(IMAGE_JXL);
27692774
} else {
27702775
/* none of the above? I give up */
27712776
return NS_ERROR_NOT_AVAILABLE;

image/test/fuzzing/TestDecoders.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,8 +129,13 @@ static int RunDecodeToSurfaceFuzzingAVIF(nsCOMPtr<nsIInputStream> inputStream) {
129129
return RunDecodeToSurfaceFuzzing(inputStream, "image/avif");
130130
}
131131

132+
static int RunDecodeToSurfaceFuzzingJXL(nsCOMPtr<nsIInputStream> inputStream) {
133+
return RunDecodeToSurfaceFuzzing(inputStream, "image/jxl");
134+
}
135+
132136
int FuzzingInitImage(int* argc, char*** argv) {
133137
Preferences::SetBool("image.avif.enabled", true);
138+
Preferences::SetBool("image.jxl.enabled", true);
134139

135140
nsCOMPtr<imgITools> imgTools =
136141
do_CreateInstance("@mozilla.org/image/tools;1");
@@ -162,3 +167,6 @@ MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingWebP,
162167

163168
MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingAVIF,
164169
ImageAVIF);
170+
171+
MOZ_FUZZING_INTERFACE_STREAM(FuzzingInitImage, RunDecodeToSurfaceFuzzingJXL,
172+
ImageJXL);

image/test/gtest/Common.cpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,10 @@ AutoInitializeImageLib::AutoInitializeImageLib() {
4646
rv = Preferences::SetBool("image.avif.enabled", true);
4747
EXPECT_TRUE(rv == NS_OK);
4848

49+
// Ensure JXL is enabled to run decoder tests.
50+
rv = Preferences::SetBool("image.jxl.enabled", true);
51+
EXPECT_TRUE(rv == NS_OK);
52+
4953
// Ensure that ImageLib services are initialized.
5054
nsCOMPtr<imgITools> imgTools =
5155
do_CreateInstance("@mozilla.org/image/tools;1");
@@ -437,6 +441,10 @@ ImageTestCase GreenAVIFTestCase() {
437441
.WithSurfaceFlags(SurfaceFlags::TO_SRGB_COLORSPACE);
438442
}
439443

444+
ImageTestCase GreenJXLTestCase() {
445+
return ImageTestCase("green.jxl", "image/jxl", IntSize(100, 100));
446+
}
447+
440448
// Forcing sRGB is required until nsAVIFDecoder supports ICC profiles
441449
// See bug 1634741
442450
ImageTestCase Transparent10bit420AVIFTestCase() {
@@ -564,6 +572,11 @@ ImageTestCase LargeAVIFTestCase() {
564572
TEST_CASE_IGNORE_OUTPUT);
565573
}
566574

575+
ImageTestCase LargeJXLTestCase() {
576+
return ImageTestCase("large.jxl", "image/jxl", IntSize(1200, 660),
577+
TEST_CASE_IGNORE_OUTPUT);
578+
}
579+
567580
ImageTestCase GreenWebPIccSrgbTestCase() {
568581
return ImageTestCase("green.icc_srgb.webp", "image/webp", IntSize(100, 100));
569582
}
@@ -660,6 +673,11 @@ ImageTestCase TransparentWebPTestCase() {
660673
return test;
661674
}
662675

676+
ImageTestCase TransparentJXLTestCase() {
677+
return ImageTestCase("transparent.jxl", "image/jxl", IntSize(1200, 1200),
678+
TEST_CASE_IS_TRANSPARENT);
679+
}
680+
663681
ImageTestCase TransparentNoAlphaHeaderWebPTestCase() {
664682
ImageTestCase test("transparent-no-alpha-header.webp", "image/webp",
665683
IntSize(100, 100), TEST_CASE_IS_FUZZY);
@@ -749,6 +767,11 @@ ImageTestCase DownscaledAVIFTestCase() {
749767
IntSize(20, 20));
750768
}
751769

770+
ImageTestCase DownscaledJXLTestCase() {
771+
return ImageTestCase("downscaled.jxl", "image/jxl", IntSize(100, 100),
772+
IntSize(20, 20));
773+
}
774+
752775
ImageTestCase DownscaledTransparentICOWithANDMaskTestCase() {
753776
// This test case is an ICO with AND mask transparency. We want to ensure that
754777
// we can downscale it without crashing or triggering ASAN failures, but its

0 commit comments

Comments
 (0)