// Dithering algorithms // Dithering is the process of reducing the colorspace of an image to a more // restricted palette. Since the Game Boy Printer only has a 2bit colorspace // available, it's important that some dithering algorithms are implemented to // deal with the fact. Depending on the properties of the dithering algorithm // detail may or may not be preserved as well. Using simple techniques like // average dithering will yield results with lots of color banding, while employing // error diffusion techniques such as Floyd Steinberg will produce smoother // results (color banding is replaced by the introduction of noise) // The wikipedia article (http://en.wikipedia.org/wiki/Dither) is quite interesting // and talks about some dithering algorithms, some of which are implemented here. // Floyd Steinberg is an error diffusion dithering algorithm that adds the error // of each color conversion to the neighbours of each pixel. This way it's possible // to achieve a finer graded dithering func ditheringFloydSteinberg(img *image.Gray, p *color.Palette) { size := img.Bounds() for y := size.Min.Y; y < size.Max.Y; y++ { for x := size.Min.X; x < size.Max.X; x++ { c1 := img.GrayAt(x, y) c2 := p.Convert(c1).(color.Gray) delta := int(c1.Y) - int(c2.Y) switch { case x+1 < size.Max.X && y < size.Max.Y: img.SetGray(x+1, y, color.Gray{uint8(int(img.GrayAt(x+1, y).Y) + delta*7/16)}) fallthrough case x < size.Max.X && y+1 < size.Max.Y: img.SetGray(x, y+1, color.Gray{uint8(int(img.GrayAt(x, y+1).Y) + delta*5/16)}) fallthrough case x-1 >= size.Min.X && y+1 < size.Max.Y: img.SetGray(x-1, y+1, color.Gray{uint8(int(img.GrayAt(x-1, y+1).Y) + delta*3/16)}) fallthrough case x+1 < size.Max.X && y+1 < size.Max.Y: img.SetGray(x+1, y+1, color.Gray{uint8(int(img.GrayAt(x+1, y+1).Y) + delta*1/16)}) } img.Set(x, y, c2) } } }
func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) { r, err := zlib.NewReader(idat) if err != nil { return nil, err } defer r.Close() bpp := 0 // Bytes per pixel. maxPalette := uint8(0) var ( gray *image.Gray rgba *image.RGBA paletted *image.Paletted nrgba *image.NRGBA gray16 *image.Gray16 rgba64 *image.RGBA64 nrgba64 *image.NRGBA64 img image.Image ) switch d.cb { case cbG8: bpp = 1 gray = image.NewGray(d.width, d.height) img = gray case cbTC8: bpp = 3 rgba = image.NewRGBA(d.width, d.height) img = rgba case cbP8: bpp = 1 paletted = image.NewPaletted(d.width, d.height, d.palette) img = paletted maxPalette = uint8(len(d.palette) - 1) case cbTCA8: bpp = 4 nrgba = image.NewNRGBA(d.width, d.height) img = nrgba case cbG16: bpp = 2 gray16 = image.NewGray16(d.width, d.height) img = gray16 case cbTC16: bpp = 6 rgba64 = image.NewRGBA64(d.width, d.height) img = rgba64 case cbTCA16: bpp = 8 nrgba64 = image.NewNRGBA64(d.width, d.height) img = nrgba64 } // cr and pr are the bytes for the current and previous row. // The +1 is for the per-row filter type, which is at cr[0]. cr := make([]uint8, 1+bpp*d.width) pr := make([]uint8, 1+bpp*d.width) for y := 0; y < d.height; y++ { // Read the decompressed bytes. _, err := io.ReadFull(r, cr) if err != nil { return nil, err } // Apply the filter. cdat := cr[1:] pdat := pr[1:] switch cr[0] { case ftNone: // No-op. case ftSub: for i := bpp; i < len(cdat); i++ { cdat[i] += cdat[i-bpp] } case ftUp: for i := 0; i < len(cdat); i++ { cdat[i] += pdat[i] } case ftAverage: for i := 0; i < bpp; i++ { cdat[i] += pdat[i] / 2 } for i := bpp; i < len(cdat); i++ { cdat[i] += uint8((int(cdat[i-bpp]) + int(pdat[i])) / 2) } case ftPaeth: for i := 0; i < bpp; i++ { cdat[i] += paeth(0, pdat[i], 0) } for i := bpp; i < len(cdat); i++ { cdat[i] += paeth(cdat[i-bpp], pdat[i], pdat[i-bpp]) } default: return nil, FormatError("bad filter type") } // Convert from bytes to colors. switch d.cb { case cbG8: for x := 0; x < d.width; x++ { gray.Set(x, y, image.GrayColor{cdat[x]}) } case cbTC8: for x := 0; x < d.width; x++ { rgba.Set(x, y, image.RGBAColor{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff}) } case cbP8: for x := 0; x < d.width; x++ { if cdat[x] > maxPalette { return nil, FormatError("palette index out of range") } paletted.SetColorIndex(x, y, cdat[x]) } case cbTCA8: for x := 0; x < d.width; x++ { nrgba.Set(x, y, image.NRGBAColor{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[4*x+3]}) } case cbG16: for x := 0; x < d.width; x++ { ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) gray16.Set(x, y, image.Gray16Color{ycol}) } case cbTC16: for x := 0; x < d.width; x++ { rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) rgba64.Set(x, y, image.RGBA64Color{rcol, gcol, bcol, 0xffff}) } case cbTCA16: for x := 0; x < d.width; x++ { rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1]) gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3]) bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5]) acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7]) nrgba64.Set(x, y, image.NRGBA64Color{rcol, gcol, bcol, acol}) } } // The current row for y is the previous row for y+1. pr, cr = cr, pr } return img, nil }
func (d *decoder) idatReader(idat io.Reader) (image.Image, os.Error) { r, err := zlib.NewReader(idat) if err != nil { return nil, err } defer r.Close() bitsPerPixel := 0 maxPalette := uint8(0) var ( gray *image.Gray rgba *image.RGBA paletted *image.Paletted nrgba *image.NRGBA gray16 *image.Gray16 rgba64 *image.RGBA64 nrgba64 *image.NRGBA64 img image.Image ) switch d.cb { case cbG1, cbG2, cbG4, cbG8: bitsPerPixel = d.depth gray = image.NewGray(d.width, d.height) img = gray case cbGA8: bitsPerPixel = 16 nrgba = image.NewNRGBA(d.width, d.height) img = nrgba case cbTC8: bitsPerPixel = 24 rgba = image.NewRGBA(d.width, d.height) img = rgba case cbP1, cbP2, cbP4, cbP8: bitsPerPixel = d.depth paletted = image.NewPaletted(d.width, d.height, d.palette) img = paletted maxPalette = uint8(len(d.palette) - 1) case cbTCA8: bitsPerPixel = 32 nrgba = image.NewNRGBA(d.width, d.height) img = nrgba case cbG16: bitsPerPixel = 16 gray16 = image.NewGray16(d.width, d.height) img = gray16 case cbGA16: bitsPerPixel = 32 nrgba64 = image.NewNRGBA64(d.width, d.height) img = nrgba64 case cbTC16: bitsPerPixel = 48 rgba64 = image.NewRGBA64(d.width, d.height) img = rgba64 case cbTCA16: bitsPerPixel = 64 nrgba64 = image.NewNRGBA64(d.width, d.height) img = nrgba64 } bytesPerPixel := (bitsPerPixel + 7) / 8 // cr and pr are the bytes for the current and previous row. // The +1 is for the per-row filter type, which is at cr[0]. cr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8) pr := make([]uint8, 1+(bitsPerPixel*d.width+7)/8) for y := 0; y < d.height; y++ { // Read the decompressed bytes. _, err := io.ReadFull(r, cr) if err != nil { return nil, err } // Apply the filter. cdat := cr[1:] pdat := pr[1:] switch cr[0] { case ftNone: // No-op. case ftSub: for i := bytesPerPixel; i < len(cdat); i++ { cdat[i] += cdat[i-bytesPerPixel] } case ftUp: for i := 0; i < len(cdat); i++ { cdat[i] += pdat[i] } case ftAverage: for i := 0; i < bytesPerPixel; i++ { cdat[i] += pdat[i] / 2 } for i := bytesPerPixel; i < len(cdat); i++ { cdat[i] += uint8((int(cdat[i-bytesPerPixel]) + int(pdat[i])) / 2) } case ftPaeth: for i := 0; i < bytesPerPixel; i++ { cdat[i] += paeth(0, pdat[i], 0) } for i := bytesPerPixel; i < len(cdat); i++ { cdat[i] += paeth(cdat[i-bytesPerPixel], pdat[i], pdat[i-bytesPerPixel]) } default: return nil, FormatError("bad filter type") } // Convert from bytes to colors. switch d.cb { case cbG1: for x := 0; x < d.width; x += 8 { b := cdat[x/8] for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ { gray.Set(x+x2, y, image.GrayColor{(b >> 7) * 0xff}) b <<= 1 } } case cbG2: for x := 0; x < d.width; x += 4 { b := cdat[x/4] for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ { gray.Set(x+x2, y, image.GrayColor{(b >> 6) * 0x55}) b <<= 2 } } case cbG4: for x := 0; x < d.width; x += 2 { b := cdat[x/2] for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ { gray.Set(x+x2, y, image.GrayColor{(b >> 4) * 0x11}) b <<= 4 } } case cbG8: for x := 0; x < d.width; x++ { gray.Set(x, y, image.GrayColor{cdat[x]}) } case cbGA8: for x := 0; x < d.width; x++ { ycol := cdat[2*x+0] nrgba.Set(x, y, image.NRGBAColor{ycol, ycol, ycol, cdat[2*x+1]}) } case cbTC8: for x := 0; x < d.width; x++ { rgba.Set(x, y, image.RGBAColor{cdat[3*x+0], cdat[3*x+1], cdat[3*x+2], 0xff}) } case cbP1: for x := 0; x < d.width; x += 8 { b := cdat[x/8] for x2 := 0; x2 < 8 && x+x2 < d.width; x2++ { idx := b >> 7 if idx > maxPalette { return nil, FormatError("palette index out of range") } paletted.SetColorIndex(x+x2, y, idx) b <<= 1 } } case cbP2: for x := 0; x < d.width; x += 4 { b := cdat[x/4] for x2 := 0; x2 < 4 && x+x2 < d.width; x2++ { idx := b >> 6 if idx > maxPalette { return nil, FormatError("palette index out of range") } paletted.SetColorIndex(x+x2, y, idx) b <<= 2 } } case cbP4: for x := 0; x < d.width; x += 2 { b := cdat[x/2] for x2 := 0; x2 < 2 && x+x2 < d.width; x2++ { idx := b >> 4 if idx > maxPalette { return nil, FormatError("palette index out of range") } paletted.SetColorIndex(x+x2, y, idx) b <<= 4 } } case cbP8: for x := 0; x < d.width; x++ { if cdat[x] > maxPalette { return nil, FormatError("palette index out of range") } paletted.SetColorIndex(x, y, cdat[x]) } case cbTCA8: for x := 0; x < d.width; x++ { nrgba.Set(x, y, image.NRGBAColor{cdat[4*x+0], cdat[4*x+1], cdat[4*x+2], cdat[4*x+3]}) } case cbG16: for x := 0; x < d.width; x++ { ycol := uint16(cdat[2*x+0])<<8 | uint16(cdat[2*x+1]) gray16.Set(x, y, image.Gray16Color{ycol}) } case cbGA16: for x := 0; x < d.width; x++ { ycol := uint16(cdat[4*x+0])<<8 | uint16(cdat[4*x+1]) acol := uint16(cdat[4*x+2])<<8 | uint16(cdat[4*x+3]) nrgba64.Set(x, y, image.NRGBA64Color{ycol, ycol, ycol, acol}) } case cbTC16: for x := 0; x < d.width; x++ { rcol := uint16(cdat[6*x+0])<<8 | uint16(cdat[6*x+1]) gcol := uint16(cdat[6*x+2])<<8 | uint16(cdat[6*x+3]) bcol := uint16(cdat[6*x+4])<<8 | uint16(cdat[6*x+5]) rgba64.Set(x, y, image.RGBA64Color{rcol, gcol, bcol, 0xffff}) } case cbTCA16: for x := 0; x < d.width; x++ { rcol := uint16(cdat[8*x+0])<<8 | uint16(cdat[8*x+1]) gcol := uint16(cdat[8*x+2])<<8 | uint16(cdat[8*x+3]) bcol := uint16(cdat[8*x+4])<<8 | uint16(cdat[8*x+5]) acol := uint16(cdat[8*x+6])<<8 | uint16(cdat[8*x+7]) nrgba64.Set(x, y, image.NRGBA64Color{rcol, gcol, bcol, acol}) } } // The current row for y is the previous row for y+1. pr, cr = cr, pr } return img, nil }
func addPixelsToGray(src image.Image, sx, sy int, dst image.Gray, dx, dy int) { clr := src.At(sx, sy) greyColor, _ := color.GrayModel.Convert(clr).(color.Gray) dst.Set(dx, dy, color.Gray{dst.At(dx, dy).(color.Gray).Y + greyColor.Y}) }
func main() { // Parse CLI flags and deal with basic input errors flag.Parse() if flag.Arg(0) == "" { log.Fatalln("You must specify an image src path") } if fSerial == "" && fSave == "" { log.Fatalln("You must specify either -serial or -save flags") } switch fDither { case "AVERAGE": case "FLOYDSTEINBERG": default: log.Fatalln("Specify a dithering algorithm from the list shown in -help") } // Read img source srcImg, err := openImg(flag.Arg(0)) if err != nil { log.Fatalln(err.Error()) } var saveImage, resizeRequired, rotationRequired bool // Only save image if !-serial and -save is active saveImage = fSerial == "" && fSave != "" // Detect if rotation is needed (we want img Max.Y >= Max.X) to print via serial // If !-serial and -save and -no-rotate are active, do not rotate rotationRequired = srcImg.Bounds().Max.Y < srcImg.Bounds().Max.X && !(saveImage && fNoRotate) // Resize won't be required only in the case that we want to save the image and fNoResize has // been set resizeRequired = !(saveImage && fNoResize) // Resize image, if required if resizeRequired { if rotationRequired { srcImg = resize.Resize(0, 160, srcImg, resize.Lanczos3) } else { srcImg = resize.Resize(160, 0, srcImg, resize.Lanczos3) } } size := srcImg.Bounds() var grayImg *image.Gray // Convert to grayscale & rotate (if required) // Converting to grayscale is a necessary previous step before applying // dithering. We can also rotate the image if required (the idea behind) // the rotation is that we want the Y axis of the image to be the longest // since the X axis (width) has a hard limit of 160px if rotationRequired { grayImg = image.NewGray(image.Rect(0, 0, size.Max.Y, size.Max.X)) // Convert to Gray colorspace for y := size.Min.Y; y < size.Max.Y; y++ { for x := size.Min.X; x < size.Max.X; x++ { grayImg.Set(size.Max.Y-1-y, x, srcImg.At(x, y)) } } } else { grayImg = image.NewGray(image.Rect(0, 0, size.Max.X, size.Max.Y)) // Convert to Gray colorspace for y := size.Min.Y; y < size.Max.Y; y++ { for x := size.Min.X; x < size.Max.X; x++ { grayImg.Set(x, y, srcImg.At(x, y)) } } } // Apply selected dithering algorithm switch fDither { case "AVERAGE": ditheringAverage(grayImg, &gbpPalette) case "FLOYDSTEINBERG": ditheringFloydSteinberg(grayImg, &gbpPalette) } // Save image (only if !-serial and -save) if saveImage { err = saveJpg(fSave, grayImg) if err != nil { log.Fatalln(err.Error()) } log.Printf("Transformation done. Image saved at %s\n", fSave) return // Exit here. Don't start serial communication if saving file to an external file } // Serialize transformed img into a byte array we can send to the GBPrinter // Since we can fit 4 pixels in a byte, buffer length its total number of // pixels divided by 4 imgBuffer := make([]byte, grayImg.Bounds().Max.X*grayImg.Bounds().Max.Y/4) writePixelsToBuffer(readPixelsByTiles(grayImg), imgBuffer) // Open a connection to specified serial port. Set a large timeout. Printing // takes some time after all c := &serial.Config{Name: fSerial, Baud: 9600, ReadTimeout: time.Second * 120} s, err := serial.OpenPort(c) if err != nil { log.Fatalln(err.Error()) } // We will be sending and receiving both 4 and 2 byte buffers. Create them. msgBuf4 := make([]byte, 4) msgBuf2 := make([]byte, 2) // We open the connection sending the length of the buffer we want to print. // It's a uint32 sent little endian style (smallest byte, smallest mem addr) // So we have to reverse the number for idx := range msgBuf4 { msgBuf4[idx] = byte(uint32(len(imgBuffer)) & (uint32(0xFF) << uint((3-idx)*8)) >> uint((3-idx)*8)) } log.Println(msgBuf4) _, err = s.Write(msgBuf4) if err != nil { log.Fatal(err) } // This reads the max length that is to be expected from our payload _, err = s.Read(msgBuf4) log.Println(msgBuf4) if err != nil { log.Fatal(err) } // ACK the transaction s.Write([]byte("OK")) // Receive ACK from printer _, err = s.Read(msgBuf2) log.Println(msgBuf2) if err != nil { log.Fatal(err) } // Start sending img byte buffer. payloadLength at a time. After every package // read the 4 byte response package, with the overall status and the finer // grained bit status of the printer payloadLength := 1280 for idx := 0; idx < len(imgBuffer); idx += payloadLength { var remaining int if idx+payloadLength > len(imgBuffer) { remaining = len(imgBuffer) } else { remaining = idx + payloadLength } s.Write(imgBuffer[idx:remaining]) s.Read(msgBuf4) log.Println(msgBuf4) } log.Println("DONE!") }