Skip to content

Commit 39868b4

Browse files
zondsaschanaz
authored andcommitted
Bug 1986393 - Add Rust-based JXL decoder. r=tnikkel,saschanaz
Differential Revision: https://phabricator.services.mozilla.com/D277142
1 parent 2e55118 commit 39868b4

File tree

9 files changed

+466
-3
lines changed

9 files changed

+466
-3
lines changed

image/decoders/nsJXLDecoder.cpp

Lines changed: 140 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88

99
#include "nsJXLDecoder.h"
1010

11+
#include "mozilla/CheckedInt.h"
1112
#include "RasterImage.h"
13+
#include "SurfacePipeFactory.h"
14+
#include "mozilla/Vector.h"
15+
16+
using namespace mozilla::gfx;
1217

1318
namespace mozilla::image {
1419

@@ -23,6 +28,11 @@ nsJXLDecoder::nsJXLDecoder(RasterImage* aImage)
2328
("[this=%p] nsJXLDecoder::nsJXLDecoder", this));
2429
}
2530

31+
nsresult nsJXLDecoder::InitInternal() {
32+
mDecoder.reset(jxl_decoder_new(IsMetadataDecode()));
33+
return NS_OK;
34+
}
35+
2636
nsJXLDecoder::~nsJXLDecoder() {
2737
MOZ_LOG(sJXLLog, LogLevel::Debug,
2838
("[this=%p] nsJXLDecoder::~nsJXLDecoder", this));
@@ -46,13 +56,141 @@ LexerResult nsJXLDecoder::DoDecode(SourceBufferIterator& aIterator,
4656

4757
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::ReadJXLData(
4858
const char* aData, size_t aLength) {
49-
// Stub: JXL decoder removed, waiting for Rust replacement
50-
return Transition::TerminateFailure();
59+
MOZ_ASSERT(mDecoder);
60+
61+
const uint8_t* currentData = reinterpret_cast<const uint8_t*>(aData);
62+
size_t currentLength = aLength;
63+
64+
while (true) {
65+
uint8_t* bufferPtr = mPixelBuffer.empty() ? nullptr : mPixelBuffer.begin();
66+
size_t bufferLen = mPixelBuffer.length();
67+
68+
JxlDecoderStatus status = jxl_decoder_process_data(
69+
mDecoder.get(), &currentData, &currentLength, bufferPtr, bufferLen);
70+
71+
switch (status) {
72+
case JxlDecoderStatus::Ok: {
73+
if (!HasSize()) {
74+
JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
75+
if (!basicInfo.valid) {
76+
if (currentLength == 0) {
77+
return Transition::ContinueUnbuffered(State::JXL_DATA);
78+
} else {
79+
break;
80+
}
81+
}
82+
83+
if (basicInfo.width > INT32_MAX || basicInfo.height > INT32_MAX) {
84+
return Transition::TerminateFailure();
85+
}
86+
87+
PostSize(basicInfo.width, basicInfo.height);
88+
if (basicInfo.has_alpha) {
89+
PostHasTransparency();
90+
}
91+
PostFrameCount(1); // Static JXL = 1 frame
92+
93+
if (IsMetadataDecode()) {
94+
return Transition::TerminateSuccess();
95+
}
96+
}
97+
98+
bool frameReady = jxl_decoder_is_frame_ready(mDecoder.get());
99+
100+
// Check if frame is ready for rendering and we need to allocate buffer
101+
if (HasSize() && frameReady && mPixelBuffer.empty()) {
102+
OrientedIntSize size = Size();
103+
CheckedInt<size_t> bufferSize =
104+
CheckedInt<size_t>(size.width) * size.height * 4;
105+
if (!bufferSize.isValid()) {
106+
MOZ_LOG(sJXLLog, LogLevel::Error,
107+
("[this=%p] nsJXLDecoder::ReadJXLData -- buffer size "
108+
"overflow\n",
109+
this));
110+
return Transition::TerminateFailure();
111+
}
112+
if (!mPixelBuffer.resize(bufferSize.value())) {
113+
MOZ_LOG(sJXLLog, LogLevel::Error,
114+
("[this=%p] nsJXLDecoder::ReadJXLData -- failed to "
115+
"allocate pixel buffer\n",
116+
this));
117+
return Transition::TerminateFailure();
118+
}
119+
break;
120+
}
121+
122+
// Frame rendering complete when frameReady becomes false
123+
if (HasSize() && !frameReady && !mPixelBuffer.empty()) {
124+
// The pixel buffer has been filled by jxl_decoder_process_data.
125+
// Send it through the surface pipeline and finish decoding.
126+
nsresult rv = ProcessFrame(mPixelBuffer);
127+
if (NS_FAILED(rv)) {
128+
return Transition::TerminateFailure();
129+
}
130+
131+
PostDecodeDone();
132+
return Transition::TerminateSuccess();
133+
} else if (currentLength == 0) {
134+
return Transition::ContinueUnbuffered(State::JXL_DATA);
135+
}
136+
break;
137+
}
138+
139+
case JxlDecoderStatus::NeedMoreData: {
140+
if (currentLength == 0) {
141+
return Transition::ContinueUnbuffered(State::JXL_DATA);
142+
}
143+
break;
144+
}
145+
146+
case JxlDecoderStatus::Error:
147+
return Transition::TerminateFailure();
148+
}
149+
}
51150
}
52151

53152
LexerTransition<nsJXLDecoder::State> nsJXLDecoder::FinishedJXLData() {
54153
MOZ_ASSERT_UNREACHABLE("Read the entire address space?");
55154
return Transition::TerminateFailure();
56155
}
57156

157+
nsresult nsJXLDecoder::ProcessFrame(Vector<uint8_t>& aPixelBuffer) {
158+
MOZ_ASSERT(HasSize());
159+
MOZ_ASSERT(mDecoder);
160+
161+
JxlBasicInfo basicInfo = jxl_decoder_get_basic_info(mDecoder.get());
162+
163+
OrientedIntSize size = Size();
164+
SurfaceFormat inFormat = SurfaceFormat::R8G8B8A8;
165+
SurfaceFormat outFormat =
166+
basicInfo.has_alpha ? SurfaceFormat::OS_RGBA : SurfaceFormat::OS_RGBX;
167+
SurfacePipeFlags pipeFlags = SurfacePipeFlags();
168+
169+
Maybe<SurfacePipe> pipe = SurfacePipeFactory::CreateSurfacePipe(
170+
this, size, OutputSize(), FullFrame(), inFormat, outFormat, Nothing(),
171+
nullptr, pipeFlags);
172+
if (!pipe) {
173+
return NS_ERROR_FAILURE;
174+
}
175+
176+
uint8_t* currentRow = aPixelBuffer.begin();
177+
for (int32_t y = 0; y < size.height; ++y) {
178+
WriteState result =
179+
pipe->WriteBuffer(reinterpret_cast<uint32_t*>(currentRow));
180+
if (result == WriteState::FAILURE) {
181+
return NS_ERROR_FAILURE;
182+
}
183+
currentRow += size.width * 4;
184+
}
185+
186+
if (Maybe<SurfaceInvalidRect> invalidRect = pipe->TakeInvalidRect()) {
187+
PostInvalidation(invalidRect->mInputSpaceRect,
188+
Some(invalidRect->mOutputSpaceRect));
189+
}
190+
191+
PostFrameStop(basicInfo.has_alpha ? Opacity::SOME_TRANSPARENCY
192+
: Opacity::FULLY_OPAQUE);
193+
return NS_OK;
194+
}
195+
58196
} // namespace mozilla::image

image/decoders/nsJXLDecoder.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,14 @@
99

1010
#include "Decoder.h"
1111
#include "StreamingLexer.h"
12+
#include "mozilla/Vector.h"
13+
#include "mozilla/image/jxl_decoder_ffi.h"
1214

1315
namespace mozilla::image {
14-
class RasterImage;
16+
17+
struct JxlDecoderDeleter {
18+
void operator()(JxlApiDecoder* ptr) { jxl_decoder_destroy(ptr); }
19+
};
1520

1621
class nsJXLDecoder final : public Decoder {
1722
public:
@@ -20,6 +25,7 @@ class nsJXLDecoder final : public Decoder {
2025
DecoderType GetType() const override { return DecoderType::JXL; }
2126

2227
protected:
28+
nsresult InitInternal() override;
2329
LexerResult DoDecode(SourceBufferIterator& aIterator,
2430
IResumable* aOnResume) override;
2531

@@ -28,12 +34,18 @@ class nsJXLDecoder final : public Decoder {
2834

2935
explicit nsJXLDecoder(RasterImage* aImage);
3036

37+
std::unique_ptr<JxlApiDecoder, JxlDecoderDeleter> mDecoder;
38+
3139
enum class State { JXL_DATA, FINISHED_JXL_DATA };
3240

41+
nsresult ProcessFrame(Vector<uint8_t>& aPixelBuffer);
42+
3343
LexerTransition<State> ReadJXLData(const char* aData, size_t aLength);
3444
LexerTransition<State> FinishedJXLData();
3545

3646
StreamingLexer<State> mLexer;
47+
48+
Vector<uint8_t> mPixelBuffer;
3749
};
3850

3951
} // namespace mozilla::image

image/moz.build

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,10 @@
55
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
66

77
DIRS += ["build", "decoders", "encoders", "remote"]
8+
9+
if CONFIG["MOZ_JXL"]:
10+
DIRS += ["rust/jxl"]
11+
812
TEST_DIRS += ["test/gtest"]
913

1014
if CONFIG["FUZZING_INTERFACES"]:

image/rust/jxl/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ version = "0.1.0"
44
authors = [
55
"Martin Bruse <zondolfin@gmail.com>",
66
"Kagami Sascha Rosylight <saschanaz@outlook.com>",
7+
"Sami Boukortt <sboukortt@google.com>",
78
]
89
edition = "2021"
910
license = "MPL-2.0"

image/rust/jxl/cbindgen.toml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
header = """/* This Source Code Form is subject to the terms of the Mozilla Public
2+
* License, v. 2.0. If a copy of the MPL was not distributed with this
3+
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */"""
4+
autogen_warning = """/* DO NOT MODIFY THIS MANUALLY! This file was generated using cbindgen. See RunCbindgen.py */
5+
"""
6+
include_version = true
7+
braces = "SameLine"
8+
line_length = 100
9+
tab_width = 2
10+
language = "C++"
11+
namespaces = ["mozilla", "image"]
12+
include_guard = "IMAGE_RUST_JXLDECODERFFI_H_"
13+
includes = ["cstdint", "cstddef"]
14+
15+
[export]
16+
include = ["JxlDecoderStatus", "JxlBasicInfo"]
17+
18+
[fn]
19+
rename_args = "None"

image/rust/jxl/moz.build

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2+
# vim: set filetype=python:
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+
if CONFIG["COMPILE_ENVIRONMENT"]:
8+
# Generate C++ header from Rust FFI using cbindgen
9+
CbindgenHeader(
10+
"jxl_decoder_ffi.h",
11+
inputs=["/image/rust/jxl"],
12+
)
13+
14+
# Export the generated header
15+
EXPORTS.mozilla.image += [
16+
"!jxl_decoder_ffi.h",
17+
]

0 commit comments

Comments
 (0)