mirror of
https://github.com/rivo/tview.git
synced 2025-04-24 13:48:56 +08:00
Implemented Floyd-Steinberg dithering on 8x8 matrix.
This commit is contained in:
parent
8b56f225c5
commit
cb9e0f7487
107
image.go
107
image.go
@ -9,9 +9,8 @@ import (
|
||||
|
||||
// Types of dithering applied to images.
|
||||
const (
|
||||
ImageDitheringNone = iota // No dithering.
|
||||
ImageDitheringThreshold // Grey scale thresholding at 50%.
|
||||
ImageDitheringFloydSteinberg // Floyd-Steinberg dithering (the default).
|
||||
DitheringNone = iota // No dithering.
|
||||
DitheringFloydSteinberg // Floyd-Steinberg dithering (the default).
|
||||
)
|
||||
|
||||
// The number of colors supported by true color terminals (R*G*B = 256*256*256).
|
||||
@ -114,7 +113,7 @@ type Image struct {
|
||||
func NewImage() *Image {
|
||||
return &Image{
|
||||
Box: NewBox(),
|
||||
dithering: ImageDitheringFloydSteinberg,
|
||||
dithering: DitheringFloydSteinberg,
|
||||
aspectRatio: 0.5,
|
||||
alignHorizontal: AlignCenter,
|
||||
alignVertical: AlignCenter,
|
||||
@ -156,7 +155,8 @@ func (i *Image) SetColors(colors int) *Image {
|
||||
}
|
||||
|
||||
// SetDithering sets the dithering algorithm to use, one of the constants
|
||||
// starting with "ImageDithering", for example [ImageDitheringFloydSteinberg].
|
||||
// starting with "Dithering", for example [DitheringFloydSteinberg] (the
|
||||
// default).
|
||||
func (i *Image) SetDithering(dithering int) *Image {
|
||||
i.dithering = dithering
|
||||
i.lastWidth, i.lastHeight = 0, 0
|
||||
@ -379,7 +379,12 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
for col := 0; col < i.lastWidth; col++ {
|
||||
// Calculate an error for each potential block element + color. Keep
|
||||
// the one with the lowest error.
|
||||
|
||||
// Note that the values in "resize" may lie outside [0, 1] due to
|
||||
// the error distribution during dithering.
|
||||
|
||||
minMSE := math.MaxFloat64 // Mean squared error.
|
||||
var final [64][3]float64 // The final pixel values.
|
||||
for element, bits := range blockElements {
|
||||
// Calculate the average color for the pixels covered by the set
|
||||
// bits and unset bits.
|
||||
@ -404,12 +409,21 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
bit <<= 1
|
||||
}
|
||||
}
|
||||
fg[0] /= setBits
|
||||
fg[1] /= setBits
|
||||
fg[2] /= setBits
|
||||
bg[0] /= 64 - setBits
|
||||
bg[1] /= 64 - setBits
|
||||
bg[2] /= 64 - setBits
|
||||
for ch := 0; ch < 3; ch++ {
|
||||
fg[ch] /= setBits
|
||||
if fg[ch] < 0 {
|
||||
fg[ch] = 0
|
||||
} else if fg[ch] > 1 {
|
||||
fg[ch] = 1
|
||||
}
|
||||
bg[ch] /= 64 - setBits
|
||||
if bg[ch] < 0 {
|
||||
bg[ch] = 0
|
||||
}
|
||||
if bg[ch] > 1 {
|
||||
bg[ch] = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Quantize to the nearest acceptable color.
|
||||
for _, color := range []*[3]float64{&fg, &bg} {
|
||||
@ -440,22 +454,27 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate the error.
|
||||
var mse float64
|
||||
// Calculate the error (and the final pixel values).
|
||||
var (
|
||||
mse float64
|
||||
values [64][3]float64
|
||||
valuesIndex int
|
||||
)
|
||||
bit = 1
|
||||
for y := 0; y < 8; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
if bits&bit != 0 {
|
||||
values[valuesIndex] = fg
|
||||
} else {
|
||||
values[valuesIndex] = bg
|
||||
}
|
||||
index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
|
||||
for ch := 0; ch < 3; ch++ {
|
||||
err := resized[index][ch]
|
||||
if bits&bit != 0 {
|
||||
err -= fg[ch]
|
||||
} else {
|
||||
err -= bg[ch]
|
||||
}
|
||||
err := resized[index][ch] - values[valuesIndex][ch]
|
||||
mse += err * err
|
||||
}
|
||||
bit <<= 1
|
||||
valuesIndex++
|
||||
}
|
||||
}
|
||||
|
||||
@ -463,6 +482,7 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
if mse < minMSE {
|
||||
// Yes. Save it.
|
||||
minMSE = mse
|
||||
final = values
|
||||
index := row*i.lastWidth + col
|
||||
i.pixels[index].element = element
|
||||
i.pixels[index].style = tcell.StyleDefault.
|
||||
@ -483,6 +503,13 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
}
|
||||
}
|
||||
}
|
||||
for ch := 0; ch < 3; ch++ {
|
||||
if avg[ch] < 0 {
|
||||
avg[ch] = 0
|
||||
} else if avg[ch] > 1 {
|
||||
avg[ch] = 1
|
||||
}
|
||||
}
|
||||
|
||||
// Quantize and choose shade element.
|
||||
element := BlockFullBlock
|
||||
@ -544,8 +571,12 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
fg = tcell.NewRGBColor(int32(math.Min(255, hi[0]*255)), int32(math.Min(255, hi[1]*255)), int32(math.Min(255, hi[2]*255)))
|
||||
}
|
||||
|
||||
// Calculate the error.
|
||||
var mse float64
|
||||
// Calculate the error (and the final pixel values).
|
||||
var (
|
||||
mse float64
|
||||
values [64][3]float64
|
||||
valuesIndex int
|
||||
)
|
||||
for y := 0; y < 8; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
|
||||
@ -553,16 +584,50 @@ func (i *Image) stamp(resized [][3]float64) {
|
||||
err := resized[index][ch] - avg[ch]
|
||||
mse += err * err
|
||||
}
|
||||
values[valuesIndex] = avg
|
||||
valuesIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// Is this shade element better than the block element?
|
||||
if mse < minMSE {
|
||||
// Yes. Save it.
|
||||
final = values
|
||||
index := row*i.lastWidth + col
|
||||
i.pixels[index].element = element
|
||||
i.pixels[index].style = tcell.StyleDefault.Foreground(fg).Background(bg)
|
||||
}
|
||||
|
||||
// Apply dithering.
|
||||
if i.dithering == DitheringFloydSteinberg {
|
||||
// The dithering mask determines how the error is distributed.
|
||||
// Each element has three values: dx, dy, and weight (in 16th).
|
||||
var mask = [4][3]int{
|
||||
{1, 0, 7},
|
||||
{-1, 1, 3},
|
||||
{0, 1, 5},
|
||||
{1, 1, 1},
|
||||
}
|
||||
|
||||
// We dither the whole 8x8 block, transferring errors to its
|
||||
// outer borders.
|
||||
var index int
|
||||
for y := 0; y < 8; y++ {
|
||||
for x := 0; x < 8; x++ {
|
||||
for ch := 0; ch < 3; ch++ {
|
||||
err := final[index][ch] - resized[(row*8+y)*i.lastWidth*8+(col*8+x)][ch]
|
||||
for _, dist := range mask {
|
||||
targetX, targetY := x+dist[0], y+dist[1]
|
||||
if targetX < 0 || col*8+targetX >= i.lastWidth*8 || targetY < 0 || row*8+targetY >= i.lastHeight*8 {
|
||||
continue
|
||||
}
|
||||
resized[(row*8+targetY)*i.lastWidth*8+(col*8+targetX)][ch] -= err * float64(dist[2]) / 16
|
||||
}
|
||||
}
|
||||
index++
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user