"errors"
"fmt"
"image"
- "image/color"
+ "image/draw"
"io"
"math"
- "reflect"
"log"
"os"
"sort"
_ "image/png"
"bytex64.net/code/bitsmash/packet"
+ "bytex64.net/code/bitsmash/palette"
"bytex64.net/code/bitsmash/ac"
)
return -1
}
+type EncodeOpts struct {
+ optimize int
+ palette int
+}
+
type BitSmash struct {
size int
palette int
transparent bool
- n_colors uint
packetList []packet.Packet
}
-func loadImage(filename string) image.PalettedImage {
+func findPalette(img image.Image) int {
+ selected := 0
+ minimum_error := float64(1e99)
+ size := img.Bounds().Size()
+
+ for i, p := range(palette.Palettes) {
+ error := float64(0)
+ for y := 0; y < size.Y; y++ {
+ for x := 0; x < size.X; x++ {
+ pixel_color := img.At(x, y)
+ _, _, _, a := pixel_color.RGBA()
+ if a < 0x7FFF {
+ // Don't calculate transparent pixels
+ continue
+ }
+ palette_color := p.Convert(pixel_color)
+ error += palette_color.(palette.RGB8).Distance(pixel_color)
+ }
+ }
+ log.Printf("Palette %d error %f\n", i, error);
+ if error < minimum_error {
+ selected = i
+ minimum_error = error
+ }
+ }
+
+ return selected
+}
+
+func loadImage(filename string, opts *EncodeOpts) image.PalettedImage {
reader, err := os.Open(filename)
if err != nil {
log.Fatal(err)
}
size := img.Bounds().Size()
- if !validSize(size.X) {
- log.Fatal(fmt.Sprintf("Image dimensions must be one of %v", sizes))
+ if size.X > 128 {
+ log.Fatal("Image width must be less than 128 pixels")
}
- cm := img.ColorModel()
- if reflect.TypeOf(cm).String() != "color.Palette" {
- log.Fatal("Image must be paletted")
+ // Pad the width to one of our valid sizes
+ if !validSize(size.X) {
+ var newWidth int
+ for _, s := range(sizes) {
+ if size.X < s {
+ newWidth = s
+ break
+ }
+ }
+ log.Printf("Padding image width %d to %d pixels", size.X, newWidth)
+ r := image.Rect(0, 0, newWidth, size.Y)
+ newImage := image.NewRGBA(r)
+ draw.Draw(newImage, r, image.Transparent, image.ZP, draw.Src)
+ draw.Draw(newImage, img.Bounds(), img, image.ZP, draw.Src)
+ img = newImage
}
- if len(cm.(color.Palette)) > 16 {
- log.Fatal("Palette must be 16-color or less")
+
+ // If no palette has been specified, automatically select one
+ if (opts.palette == -1) {
+ opts.palette = findPalette(img)
}
+ log.Printf("Using palette %d\n", opts.palette);
- return img.(image.PalettedImage)
+ // Convert to the selected palette
+ pImage := image.NewPaletted(img.Bounds(), palette.Palettes[opts.palette])
+ draw.Draw(pImage, img.Bounds(), img, image.ZP, draw.Src)
+
+ return pImage
}
func findAllEncodeLengths(pixels []uint8, steps int) []packet.PacketRun {
return p.Pix
}
-func NewFromPixels(pixels []uint8, size int, optimize int) BitSmash {
- highest_color := byte(0)
- for _, p := range(pixels) {
- if p > highest_color {
- highest_color = p
- }
- }
-
+func NewFromPixels(pixels []uint8, size int, opts *EncodeOpts) BitSmash {
bs := BitSmash{
size,
- 0,
+ opts.palette,
false,
- uint(highest_color + 1),
make([]packet.Packet, 0, 1),
}
i := 0
for i < len(pixels) {
- packet := findOptimalEncoding(pixels[i:], optimize)
+ packet := findOptimalEncoding(pixels[i:], opts.optimize)
i += packet.Length()
bs.packetList = append(bs.packetList, packet)
}
return bs
}
-func NewFromImage(filename string, optimize int) BitSmash {
- image := loadImage(filename)
+func NewFromImage(filename string, opts *EncodeOpts) BitSmash {
+ image := loadImage(filename, opts)
size := image.Bounds().Size().X
- return NewFromPixels(getPixelList(image), size, optimize)
+ return NewFromPixels(getPixelList(image), size, opts)
}
func (self *BitSmash) Size() image.Point {
0,
0,
false,
- 0,
nil,
}
file, err := os.Open(filename)
defer file.Close()
if err != nil {
- log.Fatal(fmt.Sprintf("Failed to open: %v", err))
+ log.Fatalf("Failed to open: %v", err)
}
err = bs.ReadFrom(file)
if err != nil {
- log.Fatal(fmt.Sprintf("Reading file: %v", err))
+ log.Fatalf("Reading file: %v", err)
}
return bs
func (self *BitSmash) codecManagerInit(c ac.CodecManager) {
c.AddModel(packet.CONTEXT_PACKET_TYPE, ac.NewModelOrder0(4))
- c.AddModel(packet.CONTEXT_COLOR, ac.NewModelOrder0(self.n_colors))
+ c.AddModel(packet.CONTEXT_COLOR, ac.NewModelOrder0(16))
c.AddModel(packet.CONTEXT_PATTERN_NUMBER, ac.NewModelOrder0PreBias(16, packet.ContextBias(packet.CONTEXT_PATTERN_NUMBER)))
c.AddModel(packet.CONTEXT_PATTERN_REPEAT, ac.NewModelOrder0PreBias(8, packet.ContextBias(packet.CONTEXT_PATTERN_REPEAT)))
c.AddModel(packet.CONTEXT_RLE_REPEAT, ac.NewModelOrder0(16))
self.transparent = ((header[0] >> 7) & 0x1) == 1
n_packets := int(header[1]) + (int(header[2]) & 0x3) + 1
- self.n_colors = (uint(header[2]) >> 2) & 0xF
self.packetList = make([]packet.Packet, n_packets)
decoder, err := ac.NewDecoder(file)
}
header[0] = byte(sizeIndex(self.size) + (self.palette << 3) + (transparent << 7))
header[1] = byte((n_packets - 1) & 0xFF)
- header[2] = byte(((n_packets - 1) >> 8) & 0x3 + (int(self.n_colors) << 2))
+ header[2] = byte(((n_packets - 1) >> 8) & 0x3)
file.Write(header[:])
func (self *BitSmash) Dump() {
self.unpackRepeat()
size := self.Size()
- fmt.Printf("%d pixels, %d colors, %dx%d\n", self.Length(), self.n_colors, size.X, size.Y)
+ fmt.Printf("%d pixels, %dx%d\n", self.Length(), size.X, size.Y)
fmt.Printf("%d packets:\n", len(self.packetList))
c := 0
dump bool
base64 bool
files []string
- opt int
+ encodeOpts EncodeOpts
}
func parseArgs() BitsmashOpts {
bo := BitsmashOpts{}
+ bo.encodeOpts = EncodeOpts{}
flag.BoolVar(&bo.decode, "decode", false, "Decode from bitsmash input rather than encode image")
flag.BoolVar(&bo.dump, "dump", false, "dump detailed information about encoding")
flag.BoolVar(&bo.base64, "base64", false, "Encode the result with base64")
- flag.IntVar(&bo.opt, "o", 3, "Optimization level")
+ flag.IntVar(&bo.encodeOpts.optimize, "o", 3, "Optimization level")
+ flag.IntVar(&bo.encodeOpts.palette, "p", -1, "Force palette N")
flag.Parse()
bo.files = flag.Args()
return bo
if (args.decode) {
smash = NewFromBSFile(args.files[0])
} else {
- smash = NewFromImage(args.files[0], args.opt)
+ smash = NewFromImage(args.files[0], &args.encodeOpts)
}
if args.dump {