func createANSImage(img image.Image, bg color.Color) (*ANSImage, error) {
	var rgbaOut *image.RGBA
	bounds := img.Bounds()
	if _, _, _, a := bg.RGBA(); a >= 0xffff {
		rgbaOut = image.NewRGBA(bounds)
		draw.Draw(rgbaOut, bounds, image.NewUniform(bg), image.ZP, draw.Src)
		draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Over)
	} else {
		if v, ok := img.(*image.RGBA); ok {
			rgbaOut = v
		} else {
			rgbaOut = image.NewRGBA(bounds)
			draw.Draw(rgbaOut, bounds, img, image.ZP, draw.Src)
		}
	}
	yMin, xMin := bounds.Min.Y, bounds.Min.X
	yMax, xMax := bounds.Max.Y, bounds.Max.X
	yMax = yMax - yMax%2  
	ansimage, err := New(yMax, xMax, bg)
	if err != nil {
		return nil, err
	}
	for y := yMin; y < yMax; y++ {
		for x := xMin; x < xMax; x++ {
			v := rgbaOut.RGBAAt(x, y)
			if err := ansimage.SetAt(y, x, v.R, v.G, v.B, 0); err != nil {
				return nil, err
			}
		}
	}
	return ansimage, nil
}
createANSImage loads data from an image and returns an ANSImage.
Background color is used to fill when image has transparency or dithering mode is enabled
Dithering mode is used to specify the way that ANSImage render ANSI-pixels (char/block elements).
package ansimage
import (
	"errors"
	"fmt"
	"image"
	"image/color"
	"image/draw"
	_ "image/gif"   
	_ "image/jpeg"  
	_ "image/png"   
	"io"
	"strings"
	"github.com/disintegration/imaging"
	_ "golang.org/x/image/bmp"   
	_ "golang.org/x/image/tiff"  
	_ "golang.org/x/image/webp"  
)
const lowerHalfBlock = "\u2584"
const (
	ScaleModeResize = ScaleMode(iota)
	ScaleModeFill
	ScaleModeFit
)
const (
	BlockSizeY = 8
	BlockSizeX = 4
)
var (
	ErrHeightNonMoT = errors.New("ANSImage: height must be a Multiple of Two value")
	ErrInvalidBoundsMoT = errors.New("ANSImage: height or width must be >=2")
	ErrOutOfBounds = errors.New("ANSImage: out of bounds")
	errUnknownScaleMode = errors.New("ANSImage: unknown scale mode")
)
type ScaleMode uint8
type ANSIpixel struct {
	Brightness uint8
	R, G, B    uint8
	upper      bool
	source     *ANSImage
}
type ANSImage struct {
	h, w     int
	maxprocs int
	bgR      uint8
	bgG      uint8
	bgB      uint8
	pixmap   [][]*ANSIpixel
}
func (ap *ANSIpixel) Render() string {
	if ap.upper {
		return fmt.Sprintf(
			"\033[48;2;%d;%d;%dm",
			ap.R, ap.G, ap.B,
		)
	}
	return fmt.Sprintf(
		"\033[38;2;%d;%d;%dm%s",
		ap.R, ap.G, ap.B,
		lowerHalfBlock,
	)
}
func (ai *ANSImage) Height() int {
	return ai.h
}
func (ai *ANSImage) Width() int {
	return ai.w
}
func (ai *ANSImage) SetMaxProcs(max int) {
	ai.maxprocs = max
}
func (ai *ANSImage) GetMaxProcs() int {
	return ai.maxprocs
}
func (ai *ANSImage) SetAt(y, x int, r, g, b, brightness uint8) error {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		ai.pixmap[y][x].R = r
		ai.pixmap[y][x].G = g
		ai.pixmap[y][x].B = b
		ai.pixmap[y][x].Brightness = brightness
		ai.pixmap[y][x].upper = y%2 == 0
		return nil
	}
	return ErrOutOfBounds
}
func (ai *ANSImage) GetAt(y, x int) (*ANSIpixel, error) {
	if y >= 0 && y < ai.h && x >= 0 && x < ai.w {
		return &ANSIpixel{
				R:          ai.pixmap[y][x].R,
				G:          ai.pixmap[y][x].G,
				B:          ai.pixmap[y][x].B,
				Brightness: ai.pixmap[y][x].Brightness,
				upper:      ai.pixmap[y][x].upper,
				source:     ai.pixmap[y][x].source,
			},
			nil
	}
	return nil, ErrOutOfBounds
}
func (ai *ANSImage) Render() string {
	type renderData struct {
		row    int
		render string
	}
	rows := make([]string, ai.h/2)
	for y := 0; y < ai.h; y += ai.maxprocs {
		ch := make(chan renderData, ai.maxprocs)
		for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			go func(r, y int) {
				var str string
				for x := 0; x < ai.w; x++ {
					str += ai.pixmap[y][x].Render()    
					str += ai.pixmap[y+1][x].Render()  
				}
				str += "\033[0m\n"  
				ch <- renderData{row: r, render: str}
			}(r, 2*r)
		}
		for n, r := 0, y+1; (n <= ai.maxprocs) && (2*r+1 < ai.h); n, r = n+1, y+n+1 {
			data := <-ch
			rows[data.row] = data.render
		}
	}
	return strings.Join(rows, "")
}
func New(h, w int, bg color.Color) (*ANSImage, error) {
	if h%2 != 0 {
		return nil, ErrHeightNonMoT
	}
	if h < 2 || w < 2 {
		return nil, ErrInvalidBoundsMoT
	}
	r, g, b, _ := bg.RGBA()
	ansimage := &ANSImage{
		h: h, w: w,
		maxprocs: 1,
		bgR:      uint8(r),
		bgG:      uint8(g),
		bgB:      uint8(b),
		pixmap:   nil,
	}
	ansimage.pixmap = func() [][]*ANSIpixel {
		v := make([][]*ANSIpixel, h)
		for y := 0; y < h; y++ {
			v[y] = make([]*ANSIpixel, w)
			for x := 0; x < w; x++ {
				v[y][x] = &ANSIpixel{
					R:          0,
					G:          0,
					B:          0,
					Brightness: 0,
					source:     ansimage,
					upper:      y%2 == 0,
				}
			}
		}
		return v
	}()
	return ansimage, nil
}
func NewScaledFromReader(reader io.Reader, y, x int, bg color.Color, sm ScaleMode) (*ANSImage, error) {
	image, _, err := image.Decode(reader)
	if err != nil {
		return nil, err
	}
	switch sm {
	case ScaleModeResize:
		image = imaging.Resize(image, x, y, imaging.Lanczos)
	case ScaleModeFill:
		image = imaging.Fill(image, x, y, imaging.Center, imaging.Lanczos)
	case ScaleModeFit:
		image = imaging.Fit(image, x, y, imaging.Lanczos)
	default:
		panic(errUnknownScaleMode)
	}
	return createANSImage(image, bg)
}