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 return rust_png::Compression::Fast;
50 case SkPngRustEncoder::CompressionLevel::kMedium:
51 return rust_png::Compression::Default;
52 case SkPngRustEncoder::CompressionLevel::kHigh:
53 return rust_png::Compression::Best;
54 }
55 SkUNREACHABLE;
56 }
57
getDataTableEntry(const SkDataTable & table,int index)58 rust::Slice<const uint8_t> getDataTableEntry(const SkDataTable& table, int index) {
59 SkASSERT((0 <= index) && (index < table.count()));
60
61 size_t size = 0;
62 const uint8_t* entry = table.atT<uint8_t>(index, &size);
63 while (size > 0 && entry[size - 1] == 0) {
64 // Ignore trailing NUL characters - these are *not* part of Rust `&str`.
65 size--;
66 }
67
68 return rust::Slice<const uint8_t>(entry, size);
69 }
70
EncodeComments(rust_png::Writer & writer,const sk_sp<SkDataTable> & comments)71 rust_png::EncodingResult EncodeComments(rust_png::Writer& writer,
72 const sk_sp<SkDataTable>& comments) {
73 if (comments != nullptr) {
74 if (comments->count() % 2 != 0) {
75 return rust_png::EncodingResult::ParameterError;
76 }
77
78 for (int i = 0; i < comments->count() / 2; ++i) {
79 rust::Slice<const uint8_t> keyword = getDataTableEntry(*comments, 2 * i);
80 rust::Slice<const uint8_t> text = getDataTableEntry(*comments, 2 * i + 1);
81 rust_png::EncodingResult result = writer.write_text_chunk(keyword, text);
82 if (result != rust_png::EncodingResult::Success) {
83 return result;
84 }
85 }
86 }
87
88 return rust_png::EncodingResult::Success;
89 }
90
91 // This helper class adapts `SkWStream` to expose the API required by Rust FFI
92 // (i.e. the `WriteTrait` API).
93 class WriteTraitAdapterForSkWStream final : public rust_png::WriteTrait {
94 public:
95 // SAFETY: The caller needs to guarantee that `stream` will be alive for
96 // as long as `WriteTraitAdapterForSkWStream`.
WriteTraitAdapterForSkWStream(SkWStream * stream)97 explicit WriteTraitAdapterForSkWStream(SkWStream* stream) : fStream(stream) {
98 SkASSERT(fStream);
99 }
100
101 ~WriteTraitAdapterForSkWStream() override = default;
102
103 // Non-copyable and non-movable.
104 WriteTraitAdapterForSkWStream(const WriteTraitAdapterForSkWStream&) = delete;
105 WriteTraitAdapterForSkWStream& operator=(const WriteTraitAdapterForSkWStream&) = delete;
106 WriteTraitAdapterForSkWStream(WriteTraitAdapterForSkWStream&&) = delete;
107 WriteTraitAdapterForSkWStream& operator=(WriteTraitAdapterForSkWStream&&) = delete;
108
109 // Implementation of the `std::io::Read::read` method. See `RustTrait`'s
110 // doc comments and
111 // https://doc.rust-lang.org/nightly/std/io/trait.Read.html#tymethod.read
112 // for guidance on the desired implementation and behavior of this method.
write(rust::Slice<const uint8_t> buffer)113 bool write(rust::Slice<const uint8_t> buffer) override {
114 SkSpan<const uint8_t> span = ToSkSpan(buffer);
115 return fStream->write(span.data(), span.size());
116 }
117
flush()118 void flush() override { fStream->flush(); }
119
120 private:
121 SkWStream* fStream = nullptr; // Non-owning pointer.
122 };
123
124 } // namespace
125
126 // static
Make(SkWStream * dst,const SkPixmap & src,const SkPngRustEncoder::Options & options)127 std::unique_ptr<SkEncoder> SkPngRustEncoderImpl::Make(SkWStream* dst,
128 const SkPixmap& src,
129 const SkPngRustEncoder::Options& options) {
130 if (!SkPixmapIsValid(src)) {
131 return nullptr;
132 }
133
134 std::optional<TargetInfo> maybeTargetInfo = SkPngEncoderBase::getTargetInfo(src.info());
135 if (!maybeTargetInfo.has_value()) {
136 return nullptr;
137 }
138 const SkEncodedInfo& dstInfo = maybeTargetInfo->fDstInfo;
139
140 SkSafeMath safe;
141 uint32_t width = safe.castTo<uint32_t>(dstInfo.width());
142 uint32_t height = safe.castTo<uint32_t>(dstInfo.height());
143 if (!safe.ok()) {
144 return nullptr;
145 }
146
147 auto writeTraitAdapter = std::make_unique<WriteTraitAdapterForSkWStream>(dst);
148 rust::Box<rust_png::ResultOfWriter> resultOfWriter =
149 rust_png::new_writer(std::move(writeTraitAdapter),
150 width,
151 height,
152 ToColorType(dstInfo.color()),
153 dstInfo.bitsPerComponent(),
154 ToCompression(options.fCompressionLevel));
155 if (resultOfWriter->err() != rust_png::EncodingResult::Success) {
156 return nullptr;
157 }
158 rust::Box<rust_png::Writer> writer = resultOfWriter->unwrap();
159
160 if (EncodeComments(*writer, options.fComments) != rust_png::EncodingResult::Success) {
161 return nullptr;
162 }
163
164 rust::Box<rust_png::ResultOfStreamWriter> resultOfStreamWriter =
165 rust_png::convert_writer_into_stream_writer(std::move(writer));
166 if (resultOfStreamWriter->err() != rust_png::EncodingResult::Success) {
167 return nullptr;
168 }
169 rust::Box<rust_png::StreamWriter> stream_writer = resultOfStreamWriter->unwrap();
170
171 return std::make_unique<SkPngRustEncoderImpl>(
172 std::move(*maybeTargetInfo), src, std::move(stream_writer));
173 }
174
SkPngRustEncoderImpl(TargetInfo targetInfo,const SkPixmap & src,rust::Box<rust_png::StreamWriter> stream_writer)175 SkPngRustEncoderImpl::SkPngRustEncoderImpl(TargetInfo targetInfo,
176 const SkPixmap& src,
177 rust::Box<rust_png::StreamWriter> stream_writer)
178 : SkPngEncoderBase(std::move(targetInfo), src), fStreamWriter(std::move(stream_writer)) {}
179
180 SkPngRustEncoderImpl::~SkPngRustEncoderImpl() = default;
181
onEncodeRow(SkSpan<const uint8_t> row)182 bool SkPngRustEncoderImpl::onEncodeRow(SkSpan<const uint8_t> row) {
183 return fStreamWriter->write(rust::Slice<const uint8_t>(row)) ==
184 rust_png::EncodingResult::Success;
185 }
186
onFinishEncoding()187 bool SkPngRustEncoderImpl::onFinishEncoding() {
188 return rust_png::finish_encoding(std::move(fStreamWriter)) == rust_png::EncodingResult::Success;
189 }
190