blob: d493d24061026a052b27e6eef3a49fae8d387485 [file]
// Copyright 2025 The Wuffs Authors.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// https://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or https://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
//
// SPDX-License-Identifier: Apache-2.0 OR MIT
package lowleveljpeg_test
import (
"bytes"
"fmt"
"image/jpeg"
"image/png"
"log"
"os"
"github.com/google/wuffs/lib/lowleveljpeg"
)
// Example_yCbCr444 demonstrates loading an RGB PNG, converting to YCbCr and to
// DCT space and saving as a 4:4:4 JPEG. Multiple JPEG outputs from the one PNG
// input demonstrate making further transforms during conversion.
func Example_yCbCr444() {
// levels holds a rough, ad hoc measure of a coefficient's frequency, in
// DCT space. Low (or high) levels[i] mean low (or high) frequency.
//
// levels[0] == 0 is the DC coefficient. The others are AC coefficients.
levels := [64]uint8{
0, 1, 2, 3, 4, 5, 6, 7,
1, 1, 2, 3, 4, 5, 6, 7,
2, 2, 2, 3, 4, 5, 6, 7,
3, 3, 3, 3, 4, 5, 6, 7,
4, 4, 4, 4, 4, 5, 6, 7,
5, 5, 5, 5, 5, 5, 6, 7,
6, 6, 6, 6, 6, 6, 6, 7,
7, 7, 7, 7, 7, 7, 7, 7,
}
// Load the source PNG. We will make numerous JPEG encodings of transformed
// versions of this PNG image.
f, err := os.Open("../../test/data/hippopotamus.regular.png")
if err != nil {
log.Fatalf("os.Open: %v", err)
}
defer f.Close()
src, err := png.Decode(f)
if err != nil {
log.Fatalf("png.Decode: %v", err)
}
// Each example (each output JPEG) has a name fragment and a transform from
// source BlockU8 values to destination BlockI16 values.
examples := []struct {
nameFragment string
quality int
transform func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8)
}{{
// Apply the ForwardDCT with no further changes. The resultant JPEG is
// a low quality (but small in file size) version of the source PNG.
nameFragment: "0_quality25",
quality: 25,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0].ForwardDCTFrom(&src[0])
dst[1].ForwardDCTFrom(&src[1])
dst[2].ForwardDCTFrom(&src[2])
},
}, {
// Ditto, with default quality.
nameFragment: "1_quality75",
quality: 75,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0].ForwardDCTFrom(&src[0])
dst[1].ForwardDCTFrom(&src[1])
dst[2].ForwardDCTFrom(&src[2])
},
}, {
// Ditto, with high quality.
nameFragment: "2_quality100",
quality: 100,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0].ForwardDCTFrom(&src[0])
dst[1].ForwardDCTFrom(&src[1])
dst[2].ForwardDCTFrom(&src[2])
},
}, {
// Like 2_quality100 but swap the Chroma-blue and Chroma-red channels.
nameFragment: "3_swap_cb_cr",
quality: 100,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0].ForwardDCTFrom(&src[0])
dst[1].ForwardDCTFrom(&src[2]) // Note the "1" and "2".
dst[2].ForwardDCTFrom(&src[1]) // Note the "2" and "1".
},
}, {
// Set the 8×8 blocks of the Luma channel to neutral (0x80) except for
// each top-left corner, which is black (0x00). Chromas are unchanged.
nameFragment: "4_chroma_only",
quality: 100,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
s0 := lowleveljpeg.BlockU8{}
s0[0] = 0x00
for i := 1; i < 64; i++ {
s0[i] = lowleveljpeg.BlockU8NeutralValue
}
dst[0].ForwardDCTFrom(&s0)
dst[1].ForwardDCTFrom(&src[1])
dst[2].ForwardDCTFrom(&src[2])
},
}, {
// Set the Chroma channels to neutral, as well as any Luma coefficients
// that have any horizontal variation (within an 8×8 block). What
// remains are the DC and vertical-only Luma coefficients.
nameFragment: "5_vertical_luma",
quality: 100,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0].ForwardDCTFrom(&src[0])
for i := 0; i < 64; i++ {
if (i & 7) != 0 {
dst[0][i] = lowleveljpeg.BlockI16NeutralValue
}
}
dst[1] = lowleveljpeg.BlockI16{}
dst[2] = lowleveljpeg.BlockI16{}
},
}, {
// Set the Chroma channels to neutral, as well as any medium- or
// high-frequency Luma coefficients.
nameFragment: "6_low_frequency_luma",
quality: 100,
transform: func(dst *lowleveljpeg.Array3BlockI16, src *lowleveljpeg.Array3BlockU8) {
dst[0] = src[0].ForwardDCT()
for i := 0; i < 64; i++ {
if levels[i] > 2 {
dst[0][i] = 0
}
}
dst[1] = lowleveljpeg.BlockI16{}
dst[2] = lowleveljpeg.BlockI16{}
},
}}
// Process each example.
for _, example := range examples {
// Initialize the encoder.
quants := lowleveljpeg.Array2QuantizationFactors{}
quants.SetToStandardValues(example.quality)
opts := &lowleveljpeg.EncoderOptions{
QuantizationFactors: &quants,
}
bounds := src.Bounds()
buf := &bytes.Buffer{}
enc := lowleveljpeg.Encoder{}
if err := enc.Reset(buf, lowleveljpeg.ColorTypeYCbCr444, bounds.Dx(), bounds.Dy(), opts); err != nil {
log.Fatalf("enc.Reset: %v", err)
}
// Extract, transform and add each MCU (Mininum Coded Unit). For 4:4:4
// Chroma subsampling, each MCU is 8×8 and contains 3 blocks: 1 Y, 1
// Cb, 1 Cr.
srcU8s := lowleveljpeg.Array3BlockU8{}
srcI16s := lowleveljpeg.Array3BlockI16{}
for y := bounds.Min.Y; y < bounds.Max.Y; y += 8 {
for x := bounds.Min.X; x < bounds.Max.X; x += 8 {
srcU8s.ExtractYCbCrFrom(src, x, y)
example.transform(&srcI16s, &srcU8s)
if err := enc.Add3(buf, &srcI16s); err != nil {
log.Fatalf("enc.Add3: %v", err)
}
}
}
// As a basic consistency check, confirm that the resultant bytes are a
// valid JPEG. When copy/pasting this code, you can omit this step.
dstBytes := buf.Bytes()
if _, err := jpeg.Decode(bytes.NewReader(dstBytes)); err != nil {
log.Fatalf("jpeg.Decode: %v", err)
}
// Change writeTmpFiles to true if you want to visually inspect the output.
const writeTmpFiles = false
dstFilename := fmt.Sprintf("/tmp/lowleveljpeg_example_%s.jpeg", example.nameFragment)
if writeTmpFiles {
if err := os.WriteFile(dstFilename, dstBytes, 0644); err != nil {
log.Fatalf("os.WriteFile: %v", err)
}
fmt.Println("Wrote ", dstFilename)
} else {
fmt.Println("Skipped", dstFilename)
}
}
// Output:
// Skipped /tmp/lowleveljpeg_example_0_quality25.jpeg
// Skipped /tmp/lowleveljpeg_example_1_quality75.jpeg
// Skipped /tmp/lowleveljpeg_example_2_quality100.jpeg
// Skipped /tmp/lowleveljpeg_example_3_swap_cb_cr.jpeg
// Skipped /tmp/lowleveljpeg_example_4_chroma_only.jpeg
// Skipped /tmp/lowleveljpeg_example_5_vertical_luma.jpeg
// Skipped /tmp/lowleveljpeg_example_6_low_frequency_luma.jpeg
}