1 /*
2 * Copyright 2024 Google LLC.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "experimental/rust_png/encoder/impl/SkPngRustEncoderImpl.h"
9
10 #include <limits>
11 #include <memory>
12 #include <utility>
13
14 #include "experimental/rust_png/encoder/SkPngRustEncoder.h"
15 #include "experimental/rust_png/ffi/FFI.rs.h"
16 #include "experimental/rust_png/ffi/UtilsForFFI.h"
17 #include "include/core/SkSpan.h"
18 #include "include/core/SkStream.h"
19 #include "include/private/SkEncodedInfo.h"
20 #include "include/private/base/SkAssert.h"
21 #include "src/base/SkSafeMath.h"
22 #include "src/encode/SkImageEncoderPriv.h"
23 #include "third_party/rust/cxx/v1/cxx.h"
24
25 #ifdef __clang__
26 #pragma clang diagnostic error "-Wconversion"
27 #endif
28
29 namespace {
30
ToColorType(SkEncodedInfo::Color color)31 rust_png::ColorType ToColorType(SkEncodedInfo::Color color) {
32 switch (color) {
33 case SkEncodedInfo::kRGB_Color:
34 return rust_png::ColorType::Rgb;
35 case SkEncodedInfo::kRGBA_Color:
36 return rust_png::ColorType::Rgba;
37 case SkEncodedInfo::kGray_Color:
38 return rust_png::ColorType::Grayscale;
39 case SkEncodedInfo::kGrayAlpha_Color:
40 return rust_png::ColorType::GrayscaleAlpha;
41 default:
42 SkUNREACHABLE;
43 }
44 }
45
ToCompression(SkPngRustEncoder::CompressionLevel level)46 rust_png::Compression ToCompression(SkPngRustEncoder::CompressionLevel level) {
47 switch (level) {
48 case SkPngRustEncoder::CompressionLevel::kLow:
49 #ifdef SK_RUST_PNG_USE_FDEFLATE_COMPRESSION_LEVELS
50 return rust_png::Compression::Fastest;
51 #else
52 return rust_png::Compression::Fast;
53 #endif
54 case SkPngRustEncoder::CompressionLevel::kMedium:
55 #ifdef SK_RUST_PNG_USE_FDEFLATE_COMPRESSION_LEVELS
56 // Using `Fast` instead of `Balanced` because we expect that
57 // `fdeflate` will performs better than 1) `flate2` used by Rust for
58 // `Balanced` and 2) `libpng`/`zlib`-based default in Chromium. We
59 // expect this based on the documentation linked below. We plan to
60 // verify this using field trials. Doc link:
61 // https://github.com/image-rs/image-png/blob/eb9b5d7f371b88f15aaca6a8d21c58b86c400d76/src/common.rs#L331-L336
62 return rust_png::Compression::Fast;
63 #else
64 return rust_png::Compression::Balanced;
65 #endif
66 case SkPngRustEncoder::CompressionLevel::kHigh:
67 return rust_png::Compression::High;
68 }
69 SkUNREACHABLE;
70 }
71
getDataTableEntry(const SkDataTable & table,int index)72 rust::Slice<const uint8_t> getDataTableEntry(const SkDataTable& table, int index) {
73 SkASSERT((0 <= index) && (index < table.count()));
74
75 size_t size = 0;
76 const uint8_t* entry = table.atT<uint8_t>(index, &size);
77 while (size > 0 && entry[size - 1] == 0) {
78 // Ignore trailing NUL characters - these are *not* part of Rust `&str`.
79 size--;
80 }
81
82 return rust::Slice<const uint8_t>(entry, size);
83 }
84
EncodeComments(rust_png::Writer & writer,const sk_sp<SkDataTable> & comments)85 rust_png::EncodingResult EncodeComments(rust_png::Writer& writer,
86 const sk_sp<SkDataTable>& comments) {
87 if (comments != nullptr) {
88 if (comments->count() % 2 != 0) {
89 return rust_png::EncodingResult::ParameterError;
90 }
91
92 for (int i = 0; i < comments->count() / 2; ++i) {
93 rust::Slice<const uint8_t> keyword = getDataTableEntry(*comments, 2 * i);
94 rust::Slice<const uint8_t> text = getDataTableEntry(*comments, 2 * i + 1);
95 rust_png::EncodingResult result = writer.write_text_chunk(keyword, text);
96 if (result != rust_png::EncodingResult::Success) {
97 return result;
98 }
99 }
100 }
101
102 return rust_png::EncodingResult::Success;
103 }
104
105 // This helper class adapts `SkWStream` to expose the API required by Rust FFI
106 // (i.e. the `WriteTrait` API).
107 class WriteTraitAdapterForSkWStream final : public rust_png::WriteTrait {
108 public:
109 // SAFETY: The caller needs to guarantee that `stream` will be alive for
110 // as long as `WriteTraitAdapterForSkWStream`.
WriteTraitAdapterForSkWStream(SkWStream * stream)111 explicit WriteTraitAdapterForSkWStream(SkWStream* stream) : fStream(stream) {
112 SkASSERT(fStream);
113 }
114
115 ~WriteTraitAdapterForSkWStream() override = default;
116
117 // Non-copyable and non-movable.
118 WriteTraitAdapterForSkWStream(const WriteTraitAdapterForSkWStream&) = delete;
119 WriteTraitAdapterForSkWStream& operator=(const WriteTraitAdapterForSkWStream&) = delete;
120 WriteTraitAdapterForSkWStream(WriteTraitAdapterForSkWStream&&) = delete;
121 WriteTraitAdapterForSkWStream& operator=(WriteTraitAdapterForSkWStream&&) = delete;
122
123 // Implementation of the `std::io::Read::read` method. See `RustTrait`'s
124 // doc comments and
125 // https://doc.rust-lang.org/nightly/std/io/trait.Read.html#tymethod.read
126 // for guidance on the desired implementation and behavior of this method.
write(rust::Slice<const uint8_t> buffer)127 bool write(rust::Slice<const uint8_t> buffer) override {
128 SkSpan<const uint8_t> span = ToSkSpan(buffer);
129 return fStream->write(span.data(), span.size());
130 }
131
flush()132 void flush() override { fStream->flush(); }
133
134 private:
135 SkWStream* fStream = nullptr; // Non-owning pointer.
136 };
137
138 } // namespace
139
140 // static
Make(SkWStream * dst,const SkPixmap & src,const SkPngRustEncoder::Options & options)141 std::unique_ptr<SkEncoder> SkPngRustEncoderImpl::Make(SkWStream* dst,
142 const SkPixmap& src,
143 const SkPngRustEncoder::Options& options) {
144 if (!SkPixmapIsValid(src)) {
145 return nullptr;
146 }
147
148 std::optional<TargetInfo> maybeTargetInfo = SkPngEncoderBase::getTargetInfo(src.info());
149 if (!maybeTargetInfo.has_value()) {
150 return nullptr;
151 }
152 const SkEncodedInfo& dstInfo = maybeTargetInfo->fDstInfo;
153
154 SkSafeMath safe;
155 uint32_t width = safe.castTo<uint32_t>(dstInfo.width());
156 uint32_t height = safe.castTo<uint32_t>(dstInfo.height());
157 if (!safe.ok()) {
158 return nullptr;
159 }
160
161 sk_sp<SkData> encodedProfile;
162 rust::Slice<const uint8_t> encodedProfileSlice;
163 if (const SkColorSpace* colorSpace = src.colorSpace(); colorSpace && !colorSpace->isSRGB()) {
164 encodedProfile = icc_from_color_space(colorSpace, nullptr, nullptr);
165 if (encodedProfile) {
166 encodedProfileSlice =
167 rust::Slice<const uint8_t>(encodedProfile->bytes(), encodedProfile->size());
168 }
169 }
170
171 auto writeTraitAdapter = std::make_unique<WriteTraitAdapterForSkWStream>(dst);
172 rust::Box<rust_png::ResultOfWriter> resultOfWriter =
173 rust_png::new_writer(std::move(writeTraitAdapter),
174 width,
175 height,
176 ToColorType(dstInfo.color()),
177 dstInfo.bitsPerComponent(),
178 ToCompression(options.fCompressionLevel),
179 encodedProfileSlice);
180 if (resultOfWriter->err() != rust_png::EncodingResult::Success) {
181 return nullptr;
182 }
183 rust::Box<rust_png::Writer> writer = resultOfWriter->unwrap();
184
185 if (EncodeComments(*writer, options.fComments) != rust_png::EncodingResult::Success) {
186 return nullptr;
187 }
188
189 rust::Box<rust_png::ResultOfStreamWriter> resultOfStreamWriter =
190 rust_png::convert_writer_into_stream_writer(std::move(writer));
191 if (resultOfStreamWriter->err() != rust_png::EncodingResult::Success) {
192 return nullptr;
193 }
194 rust::Box<rust_png::StreamWriter> stream_writer = resultOfStreamWriter->unwrap();
195
196 return std::make_unique<SkPngRustEncoderImpl>(
197 std::move(*maybeTargetInfo), src, std::move(stream_writer));
198 }
199
SkPngRustEncoderImpl(TargetInfo targetInfo,const SkPixmap & src,rust::Box<rust_png::StreamWriter> stream_writer)200 SkPngRustEncoderImpl::SkPngRustEncoderImpl(TargetInfo targetInfo,
201 const SkPixmap& src,
202 rust::Box<rust_png::StreamWriter> stream_writer)
203 : SkPngEncoderBase(std::move(targetInfo), src), fStreamWriter(std::move(stream_writer)) {}
204
205 SkPngRustEncoderImpl::~SkPngRustEncoderImpl() = default;
206
onEncodeRow(SkSpan<const uint8_t> row)207 bool SkPngRustEncoderImpl::onEncodeRow(SkSpan<const uint8_t> row) {
208 return fStreamWriter->write(rust::Slice<const uint8_t>(row)) ==
209 rust_png::EncodingResult::Success;
210 }
211
onFinishEncoding()212 bool SkPngRustEncoderImpl::onFinishEncoding() {
213 return rust_png::finish_encoding(std::move(fStreamWriter)) == rust_png::EncodingResult::Success;
214 }
215