mirror of
https://github.com/rivo/tview.git
synced 2025-04-28 13:48:53 +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.
|
// Types of dithering applied to images.
|
||||||
const (
|
const (
|
||||||
ImageDitheringNone = iota // No dithering.
|
DitheringNone = iota // No dithering.
|
||||||
ImageDitheringThreshold // Grey scale thresholding at 50%.
|
DitheringFloydSteinberg // Floyd-Steinberg dithering (the default).
|
||||||
ImageDitheringFloydSteinberg // Floyd-Steinberg dithering (the default).
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// The number of colors supported by true color terminals (R*G*B = 256*256*256).
|
// 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 {
|
func NewImage() *Image {
|
||||||
return &Image{
|
return &Image{
|
||||||
Box: NewBox(),
|
Box: NewBox(),
|
||||||
dithering: ImageDitheringFloydSteinberg,
|
dithering: DitheringFloydSteinberg,
|
||||||
aspectRatio: 0.5,
|
aspectRatio: 0.5,
|
||||||
alignHorizontal: AlignCenter,
|
alignHorizontal: AlignCenter,
|
||||||
alignVertical: 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
|
// 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 {
|
func (i *Image) SetDithering(dithering int) *Image {
|
||||||
i.dithering = dithering
|
i.dithering = dithering
|
||||||
i.lastWidth, i.lastHeight = 0, 0
|
i.lastWidth, i.lastHeight = 0, 0
|
||||||
@ -379,7 +379,12 @@ func (i *Image) stamp(resized [][3]float64) {
|
|||||||
for col := 0; col < i.lastWidth; col++ {
|
for col := 0; col < i.lastWidth; col++ {
|
||||||
// Calculate an error for each potential block element + color. Keep
|
// Calculate an error for each potential block element + color. Keep
|
||||||
// the one with the lowest error.
|
// 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.
|
minMSE := math.MaxFloat64 // Mean squared error.
|
||||||
|
var final [64][3]float64 // The final pixel values.
|
||||||
for element, bits := range blockElements {
|
for element, bits := range blockElements {
|
||||||
// Calculate the average color for the pixels covered by the set
|
// Calculate the average color for the pixels covered by the set
|
||||||
// bits and unset bits.
|
// bits and unset bits.
|
||||||
@ -404,12 +409,21 @@ func (i *Image) stamp(resized [][3]float64) {
|
|||||||
bit <<= 1
|
bit <<= 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fg[0] /= setBits
|
for ch := 0; ch < 3; ch++ {
|
||||||
fg[1] /= setBits
|
fg[ch] /= setBits
|
||||||
fg[2] /= setBits
|
if fg[ch] < 0 {
|
||||||
bg[0] /= 64 - setBits
|
fg[ch] = 0
|
||||||
bg[1] /= 64 - setBits
|
} else if fg[ch] > 1 {
|
||||||
bg[2] /= 64 - setBits
|
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.
|
// Quantize to the nearest acceptable color.
|
||||||
for _, color := range []*[3]float64{&fg, &bg} {
|
for _, color := range []*[3]float64{&fg, &bg} {
|
||||||
@ -440,22 +454,27 @@ func (i *Image) stamp(resized [][3]float64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate the error.
|
// Calculate the error (and the final pixel values).
|
||||||
var mse float64
|
var (
|
||||||
|
mse float64
|
||||||
|
values [64][3]float64
|
||||||
|
valuesIndex int
|
||||||
|
)
|
||||||
bit = 1
|
bit = 1
|
||||||
for y := 0; y < 8; y++ {
|
for y := 0; y < 8; y++ {
|
||||||
for x := 0; x < 8; x++ {
|
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)
|
index := (row*8+y)*i.lastWidth*8 + (col*8 + x)
|
||||||
for ch := 0; ch < 3; ch++ {
|
for ch := 0; ch < 3; ch++ {
|
||||||
err := resized[index][ch]
|
err := resized[index][ch] - values[valuesIndex][ch]
|
||||||
if bits&bit != 0 {
|
|
||||||
err -= fg[ch]
|
|
||||||
} else {
|
|
||||||
err -= bg[ch]
|
|
||||||
}
|
|
||||||
mse += err * err
|
mse += err * err
|
||||||
}
|
}
|
||||||
bit <<= 1
|
bit <<= 1
|
||||||
|
valuesIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -463,6 +482,7 @@ func (i *Image) stamp(resized [][3]float64) {
|
|||||||
if mse < minMSE {
|
if mse < minMSE {
|
||||||
// Yes. Save it.
|
// Yes. Save it.
|
||||||
minMSE = mse
|
minMSE = mse
|
||||||
|
final = values
|
||||||
index := row*i.lastWidth + col
|
index := row*i.lastWidth + col
|
||||||
i.pixels[index].element = element
|
i.pixels[index].element = element
|
||||||
i.pixels[index].style = tcell.StyleDefault.
|
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.
|
// Quantize and choose shade element.
|
||||||
element := BlockFullBlock
|
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)))
|
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.
|
// Calculate the error (and the final pixel values).
|
||||||
var mse float64
|
var (
|
||||||
|
mse float64
|
||||||
|
values [64][3]float64
|
||||||
|
valuesIndex int
|
||||||
|
)
|
||||||
for y := 0; y < 8; y++ {
|
for y := 0; y < 8; y++ {
|
||||||
for x := 0; x < 8; x++ {
|
for x := 0; x < 8; x++ {
|
||||||
index := (row*8+y)*i.lastWidth*8 + (col*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]
|
err := resized[index][ch] - avg[ch]
|
||||||
mse += err * err
|
mse += err * err
|
||||||
}
|
}
|
||||||
|
values[valuesIndex] = avg
|
||||||
|
valuesIndex++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is this shade element better than the block element?
|
// Is this shade element better than the block element?
|
||||||
if mse < minMSE {
|
if mse < minMSE {
|
||||||
// Yes. Save it.
|
// Yes. Save it.
|
||||||
|
final = values
|
||||||
index := row*i.lastWidth + col
|
index := row*i.lastWidth + col
|
||||||
i.pixels[index].element = element
|
i.pixels[index].element = element
|
||||||
i.pixels[index].style = tcell.StyleDefault.Foreground(fg).Background(bg)
|
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