Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 68 additions & 3 deletions Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,40 @@ class JPEGBigEndianOutputBitStream : public Stream {
size_t m_bit_offset { 0 };
};

void interpolate(f32* component, f32 max_value, i8 start, i8 stop)
{
// We're creating a uniform (ɑ = 1) Catmull–Rom curve for the missing points.
// That means that tᵢ₊₁ = tᵢ + 1.
// Note that component[start] should be interpolated but component[stop] should not.

// p1 and p2 are set to the ceil value.
// p0 is set to the last non-max value if possible, otherwise the value of p3 for symmetry.
// The same logic is applied to p3.
f32 const p0 = start == 0 ? component[zigzag_map[stop]] : component[zigzag_map[start - 1]];
f32 const p1 = max_value;
f32 const p2 = max_value;
f32 const p3 = stop > 63 ? p0 : component[zigzag_map[stop]];

f32 const t0 = 0.0f;
f32 const t1 = 1;
f32 const t2 = 2;
f32 const t3 = 3;

f32 const step = 1. / (stop - start + 1);
f32 t = t1;
for (i8 i = start; i < stop; ++i) {
t += step;
f32 const A1 = p0 * (t1 - t) / (t1 - t0) + p1 * (t - t0) / (t1 - t0);
f32 const A2 = p1 * (t2 - t) / (t2 - t1) + p2 * (t - t1) / (t2 - t1);
f32 const A3 = p2 * (t3 - t) / (t3 - t2) + p3 * (t - t2) / (t3 - t2);
f32 const B1 = A1 * (t2 - t) / (t2 - t0) + A2 * (t - t0) / (t2 - t0);
f32 const B2 = A2 * (t3 - t) / (t3 - t1) + A3 * (t - t1) / (t3 - t1);
f32 const C = B1 * (t2 - t) / (t2 - t1) + B2 * (t - t1) / (t2 - t1);

component[zigzag_map[i]] = C;
}
}

class JPEGEncodingContext {
public:
JPEGEncodingContext(JPEGBigEndianOutputBitStream output_stream)
Expand Down Expand Up @@ -256,6 +290,35 @@ class JPEGEncodingContext {
}
}

void apply_deringing()
{
// The method used here is described at: https://kornel.ski/deringing/.

for (auto& macroblock : m_macroblocks) {
for (auto component : { macroblock.r, macroblock.g, macroblock.b }) {
static constexpr auto maximum_value = NumericLimits<u8>::max();
Optional<u8> start;
u8 i = 0;
for (; i < 64; ++i) {
if (component[zigzag_map[i]] == maximum_value) {
if (!start.has_value())
start = i;
else
continue;
} else {
if (start.has_value() && i - *start > 2) {
interpolate(component, maximum_value, *start, i);
}
start.clear();
}
}

if (start != 0 && component[zigzag_map[63]] == maximum_value)
interpolate(component, maximum_value, *start, 64);
}
}
}

ErrorOr<void> write_huffman_stream(Mode mode)
{
for (auto& float_macroblock : m_macroblocks) {
Expand Down Expand Up @@ -633,8 +696,10 @@ ErrorOr<void> add_headers(Stream& stream, JPEGEncodingContext& context, JPEGWrit
return {};
}

ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, Mode mode)
ErrorOr<void> add_image(Stream& stream, JPEGEncodingContext& context, JPEGWriter::Options const& options, Mode mode)
{
if (options.use_deringing == JPEGEncoderOptions::UseDeringing::Yes)
context.apply_deringing();
context.convert_to_ycbcr(mode);
context.fdct_and_quantization(mode);
TRY(context.write_huffman_stream(mode));
Expand All @@ -649,7 +714,7 @@ ErrorOr<void> JPEGWriter::encode(Stream& stream, Bitmap const& bitmap, Options c
JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
TRY(add_headers(stream, context, options, bitmap.size(), Mode::RGB));
TRY(context.initialize_mcu(bitmap));
TRY(add_image(stream, context, Mode::RGB));
TRY(add_image(stream, context, options, Mode::RGB));
return {};
}

Expand All @@ -658,7 +723,7 @@ ErrorOr<void> JPEGWriter::encode(Stream& stream, CMYKBitmap const& bitmap, Optio
JPEGEncodingContext context { JPEGBigEndianOutputBitStream { stream } };
TRY(add_headers(stream, context, options, bitmap.size(), Mode::CMYK));
TRY(context.initialize_mcu(bitmap));
TRY(add_image(stream, context, Mode::CMYK));
TRY(add_image(stream, context, options, Mode::CMYK));
return {};
}

Expand Down
6 changes: 6 additions & 0 deletions Userland/Libraries/LibGfx/ImageFormats/JPEGWriter.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,14 @@
namespace Gfx {

struct JPEGEncoderOptions {
enum class UseDeringing : u8 {
Yes,
No,
};

Optional<ReadonlyBytes> icc_data;
u8 quality { 75 };
UseDeringing use_deringing { UseDeringing::Yes };
};

class JPEGWriter {
Expand Down
Loading