blob: 77c319376e832e7e0dc7b7b35dbe93dfed257d32 [file]
// Copyright 2026 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 lowleveldct2x2 provides DCT (Discrete Cosine Transform) operations
// on 2×2 blocks of uint8 or int16 values.
//
// It provides a subset of the sibling github.com/google/wuffs/lib/lowleveljpeg
// package's functionality, except that it operates on 2×2 blocks instead of
// JPEG's 8×8 blocks.
package lowleveldct2x2
// ----------------
// This file is essentially a copy-and-paste of lowleveljpeg's block.go, after
// adjusting from 8×8 to 2×2.
// ----------------
// ifElse is like C's (condition ? whenTrue : whenFalse) expression.
func ifElse(condition bool, whenTrue int64, whenFalse int64) int64 {
if condition {
return whenTrue
}
return whenFalse
}
const (
fmtHex = "0123456789ABCDEF"
fmtSep = " \n" // 1 space and then a new line.
)
const (
// BlockU8NeutralValue is the neutral value for a BlockU8, which holds
// unsigned uint8 values in the range [0x00, 0xFF].
BlockU8NeutralValue = 0x80
// BlockI16NeutralValue is the neutral value for a BlockI16, which holds
// signed int16 values in the range [-0x8000, +0x7FFF].
BlockI16NeutralValue = 0
)
// BlockU8 is a 2×2 block of uint8 values, such as a block of red, green, blue
// or gray pixel values.
//
// It is indexed in XY (not DCT) space. If b is a BlockU8 then b[0] is the
// top-left corner and b[2] is one pixel below that.
type BlockU8 [4]uint8
// String returns b in human-readable form.
func (b BlockU8) String() string {
buf := [4 * 3]byte{}
for i, value := range b {
buf[(3*i)+0] = fmtHex[15&(value>>0x04)]
buf[(3*i)+1] = fmtHex[15&(value>>0x00)]
buf[(3*i)+2] = fmtSep[1&i]
}
return string(buf[:])
}
// SetToNeutral sets each element to BlockU8NeutralValue.
func (b *BlockU8) SetToNeutral() {
if b == nil {
return
}
for i := range b {
b[i] = BlockU8NeutralValue
}
}
// ForwardDCT returns the FDCT (Forward Discrete Cosine Transform) of b.
func (b *BlockU8) ForwardDCT() (ret BlockI16) {
ret.ForwardDCTFrom(b)
return ret
}
// ForwardDCTFrom sets *dst to src.ForwardDCT().
func (dst *BlockI16) ForwardDCTFrom(src *BlockU8) {
if dst == nil {
return
} else if src == nil {
dst.SetToNeutral()
return
}
// This is equivalent to (but a simpler implementation than) lowleveljpeg's
// "Iterate in DCT space (outer) and XY space (inner)" v/u/y/x-loop, except
// that it operates on 2×2 blocks instead of JPEG's 8×8 blocks. Simpler
// also means that it has slightly more accurate rounding behavior.
src0 := int32(src[0]) - 0x80
src1 := int32(src[1]) - 0x80
src2 := int32(src[2]) - 0x80
src3 := int32(src[3]) - 0x80
dst[0] = int16((+src0 + src1 + src2 + src3 + 1) >> 1)
dst[1] = int16((+src0 - src1 + src2 - src3 + 1) >> 1)
dst[2] = int16((+src0 + src1 - src2 - src3 + 1) >> 1)
dst[3] = int16((+src0 - src1 - src2 + src3 + 1) >> 1)
}
// BlockI16 is a 2×2 block of int16 values, such as a block of JPEG
// coefficients (except that it's 2×2 instead of JPEG's 8×8).
//
// It is indexed in DCT (not XY) space. If b is a BlockI16 then b[0] is the DC
// coefficient and every other element is an AC coefficient.
//
// The horizontal-only AC coefficient is b[1].
//
// The vertical-only AC coefficient is b[2].
type BlockI16 [4]int16
// String returns b in human-readable form.
func (b BlockI16) String() string {
buf := [4 * 5]byte{}
for i, value := range b {
buf[(5*i)+0] = fmtHex[15&(value>>0x0C)]
buf[(5*i)+1] = fmtHex[15&(value>>0x08)]
buf[(5*i)+2] = fmtHex[15&(value>>0x04)]
buf[(5*i)+3] = fmtHex[15&(value>>0x00)]
buf[(5*i)+4] = fmtSep[1&i]
}
return string(buf[:])
}
// Abs returns the elementwise absolute value of b.
func (b *BlockI16) Abs() (ret BlockI16) {
if b == nil {
return ret
}
for i, v := range b {
if v < 0 {
ret[i] = -v
} else {
ret[i] = +v
}
}
return ret
}
// SetToNeutral sets each element to BlockI16NeutralValue.
func (b *BlockI16) SetToNeutral() {
if b == nil {
return
}
for i := range b {
b[i] = BlockI16NeutralValue
}
}
// InverseDCT returns the IDCT (Inverse Discrete Cosine Transform) of b.
func (b *BlockI16) InverseDCT() (ret BlockU8) {
ret.InverseDCTFrom(b)
return ret
}
// InverseDCTFrom sets *dst to src.InverseDCT().
func (dst *BlockU8) InverseDCTFrom(src *BlockI16) {
if dst == nil {
return
} else if src == nil {
dst.SetToNeutral()
return
}
// This is equivalent to (but a simpler implementation than) lowleveljpeg's
// "Iterate in XY space (outer) and DCT space (inner)" y/x/v/u-loop, except
// that it operates on 2×2 blocks instead of JPEG's 8×8 blocks. Simpler
// also means that it has slightly more accurate rounding behavior.
src0 := int32(src[0])
src1 := int32(src[1])
src2 := int32(src[2])
src3 := int32(src[3])
dst[0] = biasAndClamp[((+src0+src1+src2+src3+1)>>1)&1023]
dst[1] = biasAndClamp[((+src0-src1+src2-src3+1)>>1)&1023]
dst[2] = biasAndClamp[((+src0+src1-src2-src3+1)>>1)&1023]
dst[3] = biasAndClamp[((+src0-src1-src2+src3+1)>>1)&1023]
}
// IsValid returns whether b does not contain unexpectedly extreme values for
// an 8-bit-depth JPEG - one that is invalid to pass to AddN. ForwardDCT or
// ForwardDCTFrom always returns or sets a BlockI16 that IsValid.
//
// Specifically:
// - a DC element is out of range when outside [-256, +255].
// - an AC element is out of range when outside [-255, +255].
//
// A BlockI16 is valid when none of its elements are out of range.
func (b *BlockI16) IsValid() bool {
if b == nil {
return false
}
if (b[0] < -256) || (+255 < b[0]) {
return false
}
for _, v := range b[1:] {
if (v < -255) || (+255 < v) {
return false
}
}
return true
}
// biasAndClamp[x & 1023] is (x + 0x80), clamped to the range [0x00, 0xFF], for
// a signed integer x in the range [-512, +511].
var biasAndClamp = [1024]uint8{
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F,
0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F,
0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF,
0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF,
0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF,
0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF,
0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF,
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F,
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F,
0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F,
0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F,
0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F,
0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F,
}