6 changed files with 320 additions and 7 deletions
@ -0,0 +1,290 @@
|
||||
package tdiv |
||||
|
||||
import ( |
||||
"bytes" |
||||
"fmt" |
||||
"image" |
||||
"image/gif" |
||||
"image/jpeg" |
||||
"image/png" |
||||
"io" |
||||
"strings" |
||||
) |
||||
|
||||
func getBraille(pattern string) (rune, error) { |
||||
switch pattern { |
||||
case "000000": |
||||
return ' ', nil |
||||
case "100000": |
||||
return '⠁', nil |
||||
case "001000": |
||||
return '⠂', nil |
||||
case "101000": |
||||
return '⠃', nil |
||||
case "000010": |
||||
return '⠄', nil |
||||
case "100010": |
||||
return '⠅', nil |
||||
case "001010": |
||||
return '⠆', nil |
||||
case "101010": |
||||
return '⠇', nil |
||||
case "010000": |
||||
return '⠈', nil |
||||
case "110000": |
||||
return '⠉', nil |
||||
case "011000": |
||||
return '⠊', nil |
||||
case "111000": |
||||
return '⠋', nil |
||||
case "010010": |
||||
return '⠌', nil |
||||
case "110010": |
||||
return '⠍', nil |
||||
case "011010": |
||||
return '⠎', nil |
||||
case "111010": |
||||
return '⠏', nil |
||||
case "000100": |
||||
return '⠐', nil |
||||
case "100100": |
||||
return '⠑', nil |
||||
case "001100": |
||||
return '⠒', nil |
||||
case "101100": |
||||
return '⠓', nil |
||||
case "000110": |
||||
return '⠔', nil |
||||
case "100110": |
||||
return '⠕', nil |
||||
case "001110": |
||||
return '⠖', nil |
||||
case "101110": |
||||
return '⠗', nil |
||||
case "010100": |
||||
return '⠘', nil |
||||
case "110100": |
||||
return '⠙', nil |
||||
case "011100": |
||||
return '⠚', nil |
||||
case "111100": |
||||
return '⠛', nil |
||||
case "010110": |
||||
return '⠜', nil |
||||
case "110110": |
||||
return '⠝', nil |
||||
case "011110": |
||||
return '⠞', nil |
||||
case "111110": |
||||
return '⠟', nil |
||||
case "000001": |
||||
return '⠠', nil |
||||
case "100001": |
||||
return '⠡', nil |
||||
case "001001": |
||||
return '⠢', nil |
||||
case "101001": |
||||
return '⠣', nil |
||||
case "000011": |
||||
return '⠤', nil |
||||
case "100011": |
||||
return '⠥', nil |
||||
case "001011": |
||||
return '⠦', nil |
||||
case "101011": |
||||
return '⠧', nil |
||||
case "010001": |
||||
return '⠨', nil |
||||
case "110001": |
||||
return '⠩', nil |
||||
case "011001": |
||||
return '⠪', nil |
||||
case "111001": |
||||
return '⠫', nil |
||||
case "010011": |
||||
return '⠬', nil |
||||
case "110011": |
||||
return '⠭', nil |
||||
case "011011": |
||||
return '⠮', nil |
||||
case "111011": |
||||
return '⠯', nil |
||||
case "000101": |
||||
return '⠰', nil |
||||
case "100101": |
||||
return '⠱', nil |
||||
case "001101": |
||||
return '⠲', nil |
||||
case "101101": |
||||
return '⠳', nil |
||||
case "000111": |
||||
return '⠴', nil |
||||
case "100111": |
||||
return '⠵', nil |
||||
case "001111": |
||||
return '⠶', nil |
||||
case "101111": |
||||
return '⠷', nil |
||||
case "010101": |
||||
return '⠸', nil |
||||
case "110101": |
||||
return '⠹', nil |
||||
case "011101": |
||||
return '⠺', nil |
||||
case "111101": |
||||
return '⠻', nil |
||||
case "010111": |
||||
return '⠼', nil |
||||
case "110111": |
||||
return '⠽', nil |
||||
case "011111": |
||||
return '⠾', nil |
||||
case "111111": |
||||
return '⠿', nil |
||||
default: |
||||
return '!', fmt.Errorf("Invalid character entry") |
||||
} |
||||
} |
||||
|
||||
// scaleImage loads and scales an image and returns a 2d pixel-int slice
|
||||
//
|
||||
// Adapted from:
|
||||
// http://tech-algorithm.com/articles/nearest-neighbor-image-scaling/
|
||||
func scaleImage(file io.Reader, newWidth int) (int, int, [][]int, error) { |
||||
img, _, err := image.Decode(file) |
||||
if err != nil { |
||||
return 0, 0, nil, err |
||||
} |
||||
|
||||
bounds := img.Bounds() |
||||
width, height := bounds.Max.X, bounds.Max.Y |
||||
newHeight := int(float64(newWidth) * (float64(height) / float64(width))) |
||||
|
||||
out := make([][]int, newHeight) |
||||
for i := range out { |
||||
out[i] = make([]int, newWidth) |
||||
} |
||||
|
||||
xRatio := float64(width) / float64(newWidth) |
||||
yRatio := float64(height) / float64(newHeight) |
||||
var px, py int |
||||
for i := 0; i < newHeight; i++ { |
||||
for j := 0; j < newWidth; j++ { |
||||
px = int(float64(j) * xRatio) |
||||
py = int(float64(i) * yRatio) |
||||
out[i][j] = rgbaToGray(img.At(px, py).RGBA()) |
||||
} |
||||
} |
||||
return newWidth, newHeight, out, nil |
||||
} |
||||
|
||||
// Get the bi-dimensional pixel array
|
||||
func getPixels(file io.Reader) (int, int, [][]int, error) { |
||||
img, _, err := image.Decode(file) |
||||
if err != nil { |
||||
return 0, 0, nil, err |
||||
} |
||||
|
||||
bounds := img.Bounds() |
||||
width, height := bounds.Max.X, bounds.Max.Y |
||||
|
||||
var pixels [][]int |
||||
for y := 0; y < height; y++ { |
||||
var row []int |
||||
for x := 0; x < width; x++ { |
||||
row = append(row, rgbaToGray(img.At(x, y).RGBA())) |
||||
} |
||||
pixels = append(pixels, row) |
||||
} |
||||
|
||||
return width, height, pixels, nil |
||||
} |
||||
|
||||
func errorDither(w, h int, p [][]int) [][]int { |
||||
mv := [4][2]int{ |
||||
[2]int{0, 1}, |
||||
[2]int{1, 1}, |
||||
[2]int{1, 0}, |
||||
[2]int{1, -1}, |
||||
} |
||||
per := [4]float64{0.4375, 0.0625, 0.3125, 0.1875} |
||||
var res, diff int |
||||
for y := 0; y < h; y++ { |
||||
for x := 0; x < w; x++ { |
||||
cur := p[y][x] |
||||
if cur > 128 { |
||||
res = 1 |
||||
diff = -(255 - cur) |
||||
} else { |
||||
res = 0 |
||||
diff = cur // TODO see why this was abs() in the py version
|
||||
} |
||||
for i, v := range mv { |
||||
if y+v[0] >= h || x+v[1] >= w || x+v[1] <= 0 { |
||||
continue |
||||
} |
||||
px := p[y+v[0]][x+v[1]] |
||||
px = int(float64(diff)*per[i] + float64(px)) |
||||
if px < 0 { |
||||
px = 0 |
||||
} else if px > 255 { |
||||
px = 255 |
||||
} |
||||
p[y+v[0]][x+v[1]] = px |
||||
p[y][x] = res |
||||
} |
||||
} |
||||
} |
||||
return p |
||||
} |
||||
|
||||
func toBraille(p [][]int) []rune { |
||||
w := len(p[0]) // TODO this is unsafe
|
||||
h := len(p) |
||||
rows := h / 3 |
||||
cols := w / 2 |
||||
out := make([]rune, rows*(cols+1)) |
||||
counter := 0 |
||||
for y := 0; y < h-3; y += 4 { |
||||
for x := 0; x < w-1; x += 2 { |
||||
str := fmt.Sprintf( |
||||
"%d%d%d%d%d%d", |
||||
p[y][x], p[y][x+1], |
||||
p[y+1][x], p[y+1][x+1], |
||||
p[y+2][x], p[y+2][x+1]) |
||||
b, err := getBraille(str) |
||||
if err != nil { |
||||
out[counter] = ' ' |
||||
} else { |
||||
out[counter] = b |
||||
} |
||||
counter++ |
||||
} |
||||
out[counter] = '\n' |
||||
counter++ |
||||
} |
||||
return out |
||||
} |
||||
|
||||
func rgbaToGray(r uint32, g uint32, b uint32, a uint32) int { |
||||
rf := float64(r/257) * 0.92126 |
||||
gf := float64(g/257) * 0.97152 |
||||
bf := float64(b/257) * 0.90722 |
||||
grey := int((rf + gf + bf) / 3) |
||||
return grey |
||||
} |
||||
|
||||
func Render(in []byte, width int) []string { |
||||
image.RegisterFormat("jpeg", "jpeg", jpeg.Decode, jpeg.DecodeConfig) |
||||
image.RegisterFormat("png", "png", png.Decode, png.DecodeConfig) |
||||
image.RegisterFormat("gif", "gif", gif.Decode, gif.DecodeConfig) |
||||
w, h, p, err := scaleImage(bytes.NewReader(in), width) |
||||
|
||||
if err != nil { |
||||
return []string{"Unable to render image.", "Please download using:", "", " :w ."} |
||||
} |
||||
px := errorDither(w, h, p) |
||||
b := toBraille(px) |
||||
out := strings.SplitN(string(b), "\n", -1) |
||||
return out |
||||
} |
Loading…
Reference in new issue