• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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