Esempio n. 1
0
// 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
}
Esempio n. 3
0
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
}
Esempio n. 4
0
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})
}
Esempio n. 5
0
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!")
}