Skip to content

Commit

Permalink
Initial public commit for max7456tool
Browse files Browse the repository at this point in the history
  • Loading branch information
fiam committed Aug 24, 2017
0 parents commit f2052ff
Show file tree
Hide file tree
Showing 10 changed files with 731 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/max7456tool
136 changes: 136 additions & 0 deletions build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"errors"
"fmt"
"image"
"io/ioutil"
"log"
"os"
"path/filepath"
"strconv"
"strings"

"gopkg.in/urfave/cli.v1"
)

func buildAction(ctx *cli.Context) error {
if ctx.NArg() != 2 {
return errors.New("build requires 2 arguments, see help build")
}
dir := ctx.Args().Get(0)
entries, err := ioutil.ReadDir(dir)
if err != nil {
return err
}
chars := make(map[int]*MCMChar)
var builder charBuilder
for _, e := range entries {
if e.IsDir() {
continue
}
name := e.Name()
ext := filepath.Ext(name)
if strings.ToLower(ext) == ".png" {
nonExt := name[:len(name)-len(ext)]
// Parse the name. It might contain multiple images
lines := strings.Split(nonExt, "_")
var nums [][]int
w := 0
h := len(lines)
for _, line := range lines {
var lineNums []int
items := strings.Split(line, "-")
if w != 0 && w != len(items) {
return fmt.Errorf("uneven lines in filename %q: %d vs %d", nonExt, w, len(items))
}
w = len(items)
for _, v := range items {
n, err := strconv.Atoi(v)
if err != nil {
return fmt.Errorf("invalid number %q if image filename %q: %v", v, nonExt, err)
}
lineNums = append(lineNums, n)
}
nums = append(nums, lineNums)
}
// Decode the image
filename := filepath.Join(dir, name)
imf, err := os.Open(filename)
if err != nil {
return err
}
im, imfmt, err := image.Decode(imf)
if err != nil {
return fmt.Errorf("error decoding %s: %v", filename, err)
}
if err := imf.Close(); err != nil {
return err
}
if imfmt != "png" {
return fmt.Errorf("%s: invalid image format %s, must be png", filename, imfmt)
}
if im.Bounds().Dx() != w*charWidth {
return fmt.Errorf("image with %d characters per line must have a width of %d, not %d", w, w*charWidth, im.Bounds().Dx())
}
if im.Bounds().Dy() != h*charHeight {
return fmt.Errorf("image with %d characters per column must have a height of %d, not %d", h, h*charHeight, im.Bounds().Dy())
}
bounds := im.Bounds()
// Import each character
for jj := 0; jj < h; jj++ {
for ii := 0; ii < w; ii++ {
chNum := nums[jj][ii]
x0 := bounds.Min.X + ii*charWidth
y0 := bounds.Min.Y + jj*charHeight
if debugFlag {
log.Printf("importing char %d from image %v @%d,%d", chNum, name, x0, y0)
}
builder.Reset()
for y := y0; y < y0+charHeight; y++ {
for x := x0; x < x0+charWidth; x++ {
r, g, b, a := im.At(x, y).RGBA()
var p MCMPixel
switch {
case r == 0 && g == 0 && b == 0 && a == 65535:
p = MCMPixelBlack
case r == 65535 && g == 65535 && b == 65535 && a == 65535:
p = MCMPixelWhite
default:
p = MCMPixelTransparent
}
builder.AppendPixel(p)
}
}
for !builder.IsComplete() {
builder.AppendPixel(MCMPixelTransparent)
}
if _, found := chars[chNum]; found {
return fmt.Errorf("duplicate character %d", chNum)
}
chars[chNum] = builder.Char()
builder.Reset()
}
}
}
}
output := ctx.Args().Get(1)
f, err := openOutputFile(output)
if err != nil {
return err
}
enc := &Encoder{
Chars: chars,
Fill: !ctx.Bool("no-blanks"),
}
if err := enc.Encode(f); err != nil {
// Remove the file, since it can't be
// a proper .mcm at this point
os.Remove(output)
return err
}
if err := f.Close(); err != nil {
return err
}
return nil
}
172 changes: 172 additions & 0 deletions char.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
package main

import (
"fmt"
"image"
"image/color"
)

const (
charWidth = 12
charHeight = 18

// See type MCMChar
minCharBytes = 54
charBytes = 64
)

var (
blankCharacter = constantChar(85) // all pixels = 01
)

// MCMPixel represents a pixel in a character. Each pixel must be one
// of MCMPixelBlack, MCMPixelTransparent or MCMPixelWhite.
type MCMPixel byte

const (
// MCMPixelBlack represents a black pixel
MCMPixelBlack MCMPixel = 0
// MCMPixelTransparent represents a transparent/gray pixel,
// depending on the OSD mode.
MCMPixelTransparent = 1
// MCMPixelWhite represents a white pixel
MCMPixelWhite = 2
)

func (p MCMPixel) isTransparent() bool {
// Transparent pixels have the LSB
// set to 1 while MSB is ignored.
return (p & MCMPixelTransparent) == MCMPixelTransparent
}

// MCMChar represents a character in the character map.
// Each character has 12x18 lines, where each pixel is represented
// by 2 bits. Thus, each character requires ((12*18)*2)/8 = 54 bytes.
// However, MCM files use 64 bytes per character ignoring the rest
// (according to Maxim, to make adressing easier).
type MCMChar struct {
data []byte
}

// Data returns a copy of the raw pixel data.
func (c *MCMChar) Data() []byte {
data := make([]byte, len(c.data))
copy(data, c.data)
return data
}

// ForEachPixel calls f for each pixel in the character.
// 0 <= x <= 12 while y >= 0. Note that a character might
// have extra ignored pixels at the end. unused will be true
// for those ones. p will always be one
// of the constants defined for MCMPixel
func (c *MCMChar) ForEachPixel(f func(x, y int, unused bool, p MCMPixel)) {
x := 0
y := 0
for _, v := range c.data {
unused := y >= charHeight
f(x, y, unused, MCMPixel((0xC0&v)>>6))
f(x+1, y, unused, MCMPixel((0x30&v)>>4))
f(x+2, y, unused, MCMPixel((0xC&v)>>2))
f(x+3, y, unused, MCMPixel(0x03&v))

x += 4
if x == charWidth {
x = 0
y++
}
}
}

// Image returns a 12x18 image of the character
func (c *MCMChar) Image(transparent color.Color) image.Image {
if isNilColor(transparent) {
transparent = defaultTransparentColor
}
im := image.NewRGBA(image.Rect(0, 0, charWidth, charHeight))
c.ForEachPixel(func(x, y int, unused bool, p MCMPixel) {
if unused {
return
}
var c color.Color
switch p {
case MCMPixelTransparent:
c = transparent
case MCMPixelBlack:
c = blackColor
case MCMPixelWhite:
c = whiteColor
default:
// Should not happen
panic(fmt.Errorf("invalid pixel %v", p))
}
im.Set(x, y, c)
})
return im
}

func (c *MCMChar) isBlank() bool {
blank := true
c.ForEachPixel(func(x, y int, unused bool, p MCMPixel) {
if p != MCMPixelTransparent {
blank = false
}
})
return blank
}

func constantChar(b byte) *MCMChar {
data := make([]byte, charBytes)
for ii := range data {
data[ii] = b
}
return &MCMChar{data: data}
}

type charBuilder struct {
c *MCMChar
pixel int
}

func (b *charBuilder) Char() *MCMChar {
return b.c
}

func (b *charBuilder) Reset() {
b.c = new(MCMChar)
b.pixel = 0
}

func (b *charBuilder) IsEmpty() bool {
return len(b.c.data) == 0
}

func (b *charBuilder) IsComplete() bool {
return len(b.c.data) == charBytes && b.pixel == 0
}

func (b *charBuilder) AppendPixel(p MCMPixel) error {
if p.isTransparent() {
p = MCMPixelTransparent
}
if p > 3 {
return fmt.Errorf("invalid pixel %v > 3", p)
}
pb := byte(p)
switch b.pixel {
case 0:
// Append new byte
b.c.data = append(b.c.data, pb<<6)
case 1:
b.c.data[len(b.c.data)-1] |= pb << 4
case 2:
b.c.data[len(b.c.data)-1] |= pb << 2
case 3:
b.c.data[len(b.c.data)-1] |= pb
}
b.pixel++
if b.pixel == 4 {
b.pixel = 0
}
return nil
}
20 changes: 20 additions & 0 deletions color.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package main

import (
"image/color"
"reflect"
)

var (
blackColor = &color.RGBA{R: 0, G: 0, B: 0, A: 255}
whiteColor = &color.RGBA{R: 255, G: 255, B: 255, A: 255}
defaultTransparentColor = &color.RGBA{R: 128, G: 128, B: 128, A: 255} // 50% gray
)

func isNilColor(c color.Color) bool {
if c == nil {
return true
}
v := reflect.ValueOf(c)
return v.Kind() == reflect.Ptr && v.IsNil()
}
Loading

0 comments on commit f2052ff

Please sign in to comment.