blob: bc98fd16411d21702c72bc3293c8d1e118a38e28 [file] [log] [blame] [edit]
// 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
import (
"image"
"image/color"
)
// ExtractFrom sets dst to a single channel (Gray) 8×8 MCU (Minimum Coded
// Unit), with the given top-left corner, from the image m.
func (dst *Array1BlockU8) ExtractFrom(m image.Image, topLeftX int, topLeftY int) {
if dst == nil {
return
}
dst.SetToNeutral()
if m == nil {
return
}
bounds := m.Bounds()
mGray, _ := m.(*image.Gray)
mRGBA64Image, _ := m.(image.RGBA64Image)
for dy := 0; dy < 8; dy++ {
for dx := 0; dx < 8; dx++ {
p := image.Point{topLeftX + dx, topLeftY + dy}
if !p.In(bounds) {
continue
}
i := (8 * dy) + dx
if mGray != nil {
dst[0][i] = mGray.GrayAt(p.X, p.Y).Y
} else if mRGBA64Image != nil {
pix := mRGBA64Image.RGBA64At(p.X, p.Y)
r, g, b := uint32(pix.R), uint32(pix.G), uint32(pix.B)
y := ((19595 * r) + (38470 * g) + (7471 * b) + (1 << 15)) >> 24
dst[0][i] = uint8(y)
} else {
r, g, b, _ := m.At(p.X, p.Y).RGBA()
y := ((19595 * r) + (38470 * g) + (7471 * b) + (1 << 15)) >> 24
dst[0][i] = uint8(y)
}
}
}
fillRightAndDown(&dst[0], topLeftX, topLeftY, bounds.Max.X, bounds.Max.Y)
}
// ExtractFrom sets dst to a three channel (Luma, Chroma-blue, Chroma-red) 8×8
// 4:4:4 MCU (Minimum Coded Unit), with the given top-left corner, from the
// image m.
func (dst *Array3BlockU8) ExtractFrom(m image.Image, topLeftX int, topLeftY int) {
if dst == nil {
return
}
dst.SetToNeutral()
if m == nil {
return
}
bounds := m.Bounds()
mYCbCr, _ := m.(*image.YCbCr)
mRGBA64Image, _ := m.(image.RGBA64Image)
for dy := 0; dy < 8; dy++ {
for dx := 0; dx < 8; dx++ {
p := image.Point{topLeftX + dx, topLeftY + dy}
if !p.In(bounds) {
continue
}
i := (8 * dy) + dx
if mYCbCr != nil {
pix := mYCbCr.YCbCrAt(p.X, p.Y)
dst[0][i] = pix.Y
dst[1][i] = pix.Cb
dst[2][i] = pix.Cr
} else if mRGBA64Image != nil {
pix := mRGBA64Image.RGBA64At(p.X, p.Y)
y, cb, cr := color.RGBToYCbCr(
uint8(pix.R>>8),
uint8(pix.G>>8),
uint8(pix.B>>8),
)
dst[0][i] = y
dst[1][i] = cb
dst[2][i] = cr
} else {
r, g, b, _ := m.At(p.X, p.Y).RGBA()
y, cb, cr := color.RGBToYCbCr(
uint8(r>>8),
uint8(g>>8),
uint8(b>>8),
)
dst[0][i] = y
dst[1][i] = cb
dst[2][i] = cr
}
}
}
fillRightAndDown(&dst[0], topLeftX, topLeftY, bounds.Max.X, bounds.Max.Y)
fillRightAndDown(&dst[1], topLeftX, topLeftY, bounds.Max.X, bounds.Max.Y)
fillRightAndDown(&dst[2], topLeftX, topLeftY, bounds.Max.X, bounds.Max.Y)
}
// ExtractFrom sets dst to a three channel (Luma, Chroma-blue, Chroma-red)
// 16×16 4:2:0 MCU (Minimum Coded Unit, with the given top-left corner, from
// the image m.
//
// The 6 elements are:
// - Luma top left
// - Luma top right
// - Luma bottom left
// - Luma bottom right
// - Chroma-blue
// - Chroma-red
func (dst *Array6BlockU8) ExtractFrom(m image.Image, topLeftX int, topLeftY int) {
if dst == nil {
return
}
if m == nil {
dst.SetToNeutral()
return
}
tmp := [4]Array3BlockU8{}
tmp[0].ExtractFrom(m, topLeftX+0, topLeftY+0)
tmp[1].ExtractFrom(m, topLeftX+8, topLeftY+0)
tmp[2].ExtractFrom(m, topLeftX+0, topLeftY+8)
tmp[3].ExtractFrom(m, topLeftX+8, topLeftY+8)
dst[0] = tmp[0][0]
dst[1] = tmp[1][0]
dst[2] = tmp[2][0]
dst[3] = tmp[3][0]
downsample4x4(dst[4][0x00:], &tmp[0][1])
downsample4x4(dst[4][0x04:], &tmp[1][1])
downsample4x4(dst[4][0x20:], &tmp[2][1])
downsample4x4(dst[4][0x24:], &tmp[3][1])
downsample4x4(dst[5][0x00:], &tmp[0][2])
downsample4x4(dst[5][0x04:], &tmp[1][2])
downsample4x4(dst[5][0x20:], &tmp[2][2])
downsample4x4(dst[5][0x24:], &tmp[3][2])
}
// fillRightAndDown copies the right and bottom edge values to the full 8×8 block.
//
// For 4:2:0, we call this before chroma downsampling (from 8×8 to 4×4) to
// avoid artifacts from averaging with the neutral 0x80.
//
// For example, if mx = 3 and my = 6 then it updates b from this:
//
// AC A7 A3 80 80 80 80 80
// B6 B1 AD 80 80 80 80 80
// C0 BB B7 80 80 80 80 80
// C9 C5 C0 80 80 80 80 80
// D3 CF CA 80 80 80 80 80
// DD D9 D4 80 80 80 80 80
// 80 80 80 80 80 80 80 80
// 80 80 80 80 80 80 80 80
//
// to this:
//
// AC A7 A3 A3 A3 A3 A3 A3
// B6 B1 AD AD AD AD AD AD
// C0 BB B7 B7 B7 B7 B7 B7
// C9 C5 C0 C0 C0 C0 C0 C0
// D3 CF CA CA CA CA CA CA
// DD D9 D4 D4 D4 D4 D4 D4
// DD D9 D4 D4 D4 D4 D4 D4
// DD D9 D4 D4 D4 D4 D4 D4
func fillRightAndDown(b *BlockU8, topLeftX int, topLeftY int, bottomRightX int, bottomRightY int) {
if bottomRightX <= topLeftX {
return
}
mx := bottomRightX - topLeftX
if mx >= 8 {
mx = 8
}
if bottomRightY <= topLeftY {
return
}
my := bottomRightY - topLeftY
if my >= 8 {
my = 8
}
if mx < 8 {
for y := 0; y < my; y++ {
value := b[(8*y)|(mx-1)]
for x := mx; x < 8; x++ {
b[(8*y)|x] = value
}
}
}
if my < 8 {
src := b[(8 * (my - 1)):(8 * (my - 0))]
for y := my; y < 8; y++ {
dst := b[(8 * (y + 0)):(8 * (y + 1))]
copy(dst, src)
}
}
}
// downsample4x4 reduces one 8×8 block to one 4×4 sub-block.
func downsample4x4(dst []uint8, src *BlockU8) {
for y := 0; y < 4; y++ {
for x := 0; x < 4; x++ {
d := (y << 3) | (x << 0)
s := (y << 4) | (x << 1)
dst[d] = uint8((2 +
uint32(src[s+0x00]) +
uint32(src[s+0x01]) +
uint32(src[s+0x08]) +
uint32(src[s+0x09])) / 4)
}
}
}
// DownsampleFrom reduces one 16×16 quad-block to one 8×8 block.
func (dst *BlockU8) DownsampleFrom(src *QuadBlockU8) {
if dst == nil {
return
} else if src == nil {
dst.SetToNeutral()
return
}
for y := 0; y < 8; y++ {
for x := 0; x < 8; x++ {
d := (y << 3) | (x << 0)
s := (y << 5) | (x << 1)
dst[d] = uint8((2 +
uint32(src[s+0x00]) +
uint32(src[s+0x01]) +
uint32(src[s+0x10]) +
uint32(src[s+0x11])) / 4)
}
}
}