I am trying to render some text on a png in a Golang project using freetype/truetype. As you can see from the attachment, I am trying to render 4 letters in columns - each letter centered in the column. Have used the truetype api to get bounds and widths of the glyphs but have been unable to convert these to give me an accurate offset for each glyph. For example with the O glyph, given the font I using. I get the following dimensions:
Hmetric {AdvanceWidth:543 LeftSideBearing:36}
Bounds {XMin:0 YMin:-64 XMax:512 YMax:704}
Advance width: 512
With the last dimension being returned from GlyphBuf.
I rendered it using the following:
size := 125.00
tileOffset := (int(tileWidth) * i) + int(tileWidth/2)
pt := freetype.Pt(tileOffset, (imgH-newCharHeight)-int(size))
How can I use the glyph dimensions returned by truetype to offset the letters correctly? I have tried using the AdvanceWidth as detailed in this plotinum code (line 160) but that does not give me a consistent result across all glyphs.
As suggested by Simon the correct solution is to use AdvanceWidth:
Crude example:
package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"image"
"bufio"
"image/draw"
"image/png"
"image/color"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"github.com/golang/freetype"
"os"
)
var (
dpi = flag.Float64("dpi", 72, "screen resolution in Dots Per Inch")
fontfile = flag.String("fontfile", "/usr/share/fonts/liberation/LiberationSerif-Regular.ttf", "filename of the ttf font")
hinting = flag.String("hinting", "none", "none | full")
size = flag.Float64("size", 125, "font size in points")
spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)")
wonb = flag.Bool("whiteonblack", false, "white text on a black background")
text = string("JOJO")
)
func main() {
flag.Parse()
fmt.Printf("Loading fontfile %q\n", *fontfile)
b, err := ioutil.ReadFile(*fontfile)
if err != nil {
log.Println(err)
return
}
f, err := truetype.Parse(b)
if err != nil {
log.Println(err)
return
}
// Freetype context
fg, bg := image.Black, image.White
rgba := image.NewRGBA(image.Rect(0, 0, 1000, 200))
draw.Draw(rgba, rgba.Bounds(), bg, image.ZP, draw.Src)
c := freetype.NewContext()
c.SetDPI(*dpi)
c.SetFont(f)
c.SetFontSize(*size)
c.SetClip(rgba.Bounds())
c.SetDst(rgba)
c.SetSrc(fg)
switch *hinting {
default:
c.SetHinting(font.HintingNone)
case "full":
c.SetHinting(font.HintingFull)
}
// Make some background
// Draw the guidelines.
ruler := color.RGBA{0xdd, 0xdd, 0xdd, 0xff}
for rcount := 0; rcount < 4; rcount ++ {
for i := 0; i < 200; i++ {
rgba.Set(250*rcount, i, ruler)
}
}
// Truetype stuff
opts := truetype.Options{}
opts.Size = 125.0
face := truetype.NewFace(f, &opts)
// Calculate the widths and print to image
for i, x := range(text) {
awidth, ok := face.GlyphAdvance(rune(x))
if ok != true {
log.Println(err)
return
}
iwidthf := int(float64(awidth) / 64)
fmt.Printf("%+v\n", iwidthf)
pt := freetype.Pt(i*250+(125-iwidthf/2), 128)
c.DrawString(string(x), pt)
fmt.Printf("%+v\n", awidth)
}
// Save that RGBA image to disk.
outFile, err := os.Create("out.png")
if err != nil {
log.Println(err)
os.Exit(1)
}
defer outFile.Close()
bf := bufio.NewWriter(outFile)
err = png.Encode(bf, rgba)
if err != nil {
log.Println(err)
os.Exit(1)
}
err = bf.Flush()
if err != nil {
log.Println(err)
os.Exit(1)
}
fmt.Println("Wrote out.png OK.")
}
Related
Am looking to do custom cropping on a set of images, Instead of cropping normally, using height and width i want the flexibility of getting an output image that is cropped like a polygon or an hexagon for example, Am using the library github.com/fogleman/gg, and the built in module "image", and github.com/disintegration/imaging, but I didn't find a way to customize the cropping, i also looked for an online SaaS to do this, like imgix or imageresizer.io, but they don't seem to offer that, i know golang is the right language for this maybe i didn't look hard enough, please Help
my sample Code looks like:
var image image.Image
dc := NewContext(1000, 1000)
image = imaging.Fill(profile, 800, 750, imaging.Center, imaging.Lanczos)
// Cropping needs to happen here
dc.DrawImage(image, 123, 250)
A bit longer than expected but here you have PNG image cropping with transparent background to a rectangle. You can modify the code for different shapes by changing the getPixAlpha function.
Just add the package name and it should include the imports, then add an image test.png and it should create a test-output.png
Note: You may want to make some minor modifications for using it as a service.
type Pixel struct {
R int
G int
B int
A int
}
func LogPanic(err error, msg string) {
if err != nil {
log.Printf("ERROR: %v %s", err, msg)
panic(err)
}
}
func getPixAlpha(x, y, halfWidth int) int {
if x < halfWidth-y || x > halfWidth+y {
return 0
}
if y > halfWidth+x {
return 0
}
if x > halfWidth*3-y && y > halfWidth*3-x {
return 0
}
return int(255)
}
func getPixels(file io.Reader) ([][]Pixel, error) {
img, _, err := image.Decode(file)
LogPanic(err, "error reading image")
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
var pixels [][]Pixel
for x := 0; x < width; x++ {
var row []Pixel
for y := 0; y < height; y++ {
row = append(row, rgbaToPixel(img.At(x, y).RGBA()))
}
pixels = append(pixels, row)
}
return pixels, nil
}
func rgbaToPixel(r uint32, g uint32, b uint32, a uint32) Pixel {
return Pixel{int(r / 257), int(g / 257), int(b / 257), int(a / 257)}
}
func getRgbaPic(pixels [][]Pixel) [][]Pixel {
dx := len(pixels)
dy := len(pixels[0])
for x := 0; x < dx; x++ {
for y := 0; y < dy; y++ {
pixels[x][y].A = getPixAlpha(x, y, len(pixels)/2)
}
}
return pixels
}
func main() {
file, err := os.Open("./test.png")
LogPanic(err, "Error opening file")
defer file.Close()
pixels, err := getPixels(file)
LogPanic(err, "Error reading image")
pixels = getRgbaPic(pixels)
img := image.NewRGBA(image.Rect(0, 0, len(pixels), len(pixels[0])))
for x := 0; x < len(pixels); x++ {
for y := 0; y < len(pixels[0]); y++ {
img.Set(x, y, color.RGBA{
uint8(pixels[x][y].R),
uint8(pixels[x][y].G),
uint8(pixels[x][y].B),
uint8(pixels[x][y].A),
})
}
}
buf := &bytes.Buffer{}
err = png.Encode(buf, img)
LogPanic(err, "Error encoding")
err = ioutil.WriteFile("test-output.png", buf.Bytes(), 0666)
LogPanic(err, "Error writing file")
}
I want to process local images to make the dimension as Height = 380 PX, Width = 380 px and size is less than 20kb.
I have the following code which reads the file and then change dimension to 380 by 380 and then try to reduce the size.
But size is sometime more than 20 KB if original file is large.How to make sure that file is always less than 20 KB?
how to check size of image after dimension change and size after quality reduced image ? so that I can run a loop.
package main
import (
"bufio"
"bytes"
"fmt"
"image"
"image/jpeg"
"image/png"
"os"
"github.com/davecgh/go-spew/spew"
"github.com/nfnt/resize"
)
func main() {
// Read Local file
mainFile, err := os.Open("C://c.jpg")
if err != nil {
fmt.Println("Erro Occured")
spew.Dump(err)
return
}
defer mainFile.Close()
originalimage, _, err := image.Decode(mainFile)
if err != nil {
fmt.Println("Error ", err)
originalimage, err2 := png.Decode(mainFile)
print(err2)
print(originalimage)
}
// Create new file with reduced dimentions - height width as 380
newImage := resize.Resize(380, 380, originalimage, resize.Lanczos3)
// Create new file with reduced size - Quality down by 50%
var b bytes.Buffer
w1 := bufio.NewWriter(&b)
jpeg.Encode(w1, newImage, &jpeg.Options{50})
f2, err := os.OpenFile("C://temp5.png", os.O_WRONLY|os.O_CREATE, 0666)
if err != nil {
panic(err)
}
defer f2.Close()
b.WriteTo(f2)
}
I want to open a jpeg file from localdisk and draw a filled Rectangular box on it in Go, if i give two pixel points value like pt1(0,0) and pt2(480, 240)
Have a look here. You need to decode the image from JPEG, set the pixels in your rectangle to black, then re-encode to JPEG.
Here is a code example, it works if the image is in the same folder as the executable and you give the path like myimg.jpg:
package main
import (
"fmt"
"image"
"image/color"
"image/jpeg"
"log"
"os"
)
func main() {
err := addBlackRectangle("myimg.jpg", 500, 500, 1000, 1000)
if err != nil {
log.Fatal(err)
}
}
func addBlackRectangle(imgPath string, x1, y1, x2, y2 int) (err error) {
r, err := os.Open(imgPath)
if err != nil {
return
}
img, err := jpeg.Decode(r)
if err != nil {
return
}
rect1 := img.Bounds()
rect2 := image.Rect(x1, y1, x2, y2)
if !rect2.In(rect1) {
err = fmt.Errorf("error: rectangle outside image")
return
}
rgba := image.NewRGBA(rect1)
for x := rect1.Min.X; x <= rect1.Max.X; x++ {
for y := rect1.Min.Y; y <= rect1.Max.Y; y++ {
p := image.Pt(x, y)
if p.In(rect2) {
rgba.Set(x, y, color.Black)
} else {
rgba.Set(x, y, img.At(x, y))
}
}
}
outputFile := "rect-" + imgPath
w, err := os.Create(outputFile)
defer w.Close()
err = jpeg.Encode(w, rgba, nil)
return
}
I've been trying for sometime to open an image in binary mode with Go. In Python I'd use the Pillow and image.open() (rb mode). Example.
img = Image.open("PNG.png")
pix = img.getdata() #where 0 is black and 1 is white pixel
That would open the image with very clean binary of white and black dots like the image below. In go I've tried os.Open(file.jpg) to open the file.. I've tried decoding it with image.Decode(), I've loaded the file into bytes.Buffer, I've tried fmt.Sprintf("%b", data), all of the solutions give a byte array. Converting that byte array to binary looks nothing like the image above. I've also tried encoding/binary and its the same story with just getting bytes and the binary generated isn't what i want...
Most recently I've tried this
package main
import (
"fmt"
"image"
"image/jpeg"
"io"
"log"
"os"
)
// Pixel struct example
type Pixel struct {
R int
G int
B int
A int
}
func main() {
// You can register another format here
image.RegisterFormat("jpg", "jpg", jpeg.Decode, jpeg.DecodeConfig)
file, err := os.Open("/Users/marcsantiago/Desktop/2033bb1b194adace86f99c7bb7d72e81.jpg")
if err != nil {
log.Fatalln("Error: File could not be opened")
}
defer file.Close()
pixels, err := getPixels(file)
if err != nil {
log.Fatalln("Error: Image could not be decoded")
}
black := Pixel{0, 0, 0, 255}
for i := range pixels {
if pixels[i] == black {
fmt.Print("0")
} else {
fmt.Print("1")
}
}
}
func getPixels(file io.Reader) ([]Pixel, error) {
img, _, err := image.Decode(file)
if err != nil {
return nil, err
}
bounds := img.Bounds()
width, height := bounds.Max.X, bounds.Max.Y
var pixels []Pixel
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
pixels = append(pixels, rgbaToPixel(img.At(x, y).RGBA()))
}
}
return pixels, nil
}
// img.At(x, y).RGBA() returns four uint32 values; we want a Pixel
func rgbaToPixel(r uint32, g uint32, b uint32, a uint32) Pixel {
return Pixel{int(r / 257), int(g / 257), int(b / 257), int(a / 257)}
}
So that I can compare the binary against what I expect I converted the rgba to 1 and 0s where 0 == black... it still doesn't match up not even close. Example
Help please. I'm out of ideas. PS. This site http://www.dcode.fr/binary-image, also opens the image and generates the data I'm expecting.
UPDATE:
This is the image i'm working with..
For example,
package main
import (
"bytes"
"fmt"
"image"
"os"
_ "image/jpeg"
)
func main() {
fName := "ggk3Z.jpg"
f, err := os.Open(fName)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
defer f.Close()
img, _, err := image.Decode(f)
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
// http://www.dcode.fr/binary-image
var txt bytes.Buffer
bounds := img.Bounds()
for y := bounds.Min.Y; y < bounds.Max.Y; y++ {
for x := bounds.Min.X; x < bounds.Max.X; x++ {
r, g, b, _ := img.At(x, y).RGBA()
bin := "0"
if float64((r+g+b))/3 > 0.5 {
bin = "1"
}
txt.WriteString(bin)
}
txt.WriteString("\n")
}
fmt.Fprint(os.Stdout, txt.String())
}
The code below reads its values from this file:
2 3\n
1.0 2.0 3.0\n
-1.0 -2.0 -3.0\n
And should print:
[ {1 2 3}, {-1 -2 -3} ]
But instead I get this:
[{2 [31 2 3]} {0 []}] strconv.ParseFloat: parsing "3.0-1.0": invalid syntax
It seems that the reader.ReadLine() stays at the same location. Is there a simpler way to scan lines, then values inside each line?
package main
import (
"bufio"
"bytes"
"fmt"
"os"
"strconv"
"strings"
)
type Example struct {
classLabel int
attributes []float64
}
func NewExample(classLabel int, attributes []float64) *Example {
return &Example{classLabel, attributes}
}
func readFile(path string) ([]Example, error) {
var (
result []Example
err error
file *os.File
part []byte
size int
attributeNum int
)
if file, err = os.Open(path); err != nil {
return result, err
}
defer file.Close()
reader := bufio.NewReader(file)
buffer := bytes.NewBuffer(make([]byte, 0))
if part, _, err = reader.ReadLine(); err != nil {
return result, err
}
buffer.Write(part)
newLine := buffer.String()
fmt.Println("newLine=" + newLine)
r := strings.NewReader(newLine)
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanWords)
if scanner.Scan() {
size, err = strconv.Atoi(scanner.Text())
if err != nil {
return result, err
}
}
fmt.Println("size=" + strconv.Itoa(size))
if scanner.Scan() {
attributeNum, err = strconv.Atoi(scanner.Text())
if err != nil {
return result, err
}
}
fmt.Println("attributeNum=" + strconv.Itoa(attributeNum))
result = make([]Example, size)
var classLabel int
var attributes []float64
for k := 0; k < size; k++ {
if part, _, err = reader.ReadLine(); err != nil {
return result, err
}
buffer.Write(part)
newLine := buffer.String()
fmt.Println("newLine=" + newLine)
r := strings.NewReader(newLine)
scanner := bufio.NewScanner(r)
scanner.Split(bufio.ScanWords)
if scanner.Scan() {
classLabel, err = strconv.Atoi(scanner.Text())
if err != nil {
return result, err
}
}
fmt.Println("classLabel=" + strconv.Itoa(classLabel))
for i := 0; i < attributeNum; i++ {
var attribute float64
if scanner.Scan() {
attribute, err = strconv.ParseFloat(scanner.Text(), 64)
if err != nil {
return result, err
}
attributes = append(attributes, attribute)
fmt.Println("attribute=" + strconv.FormatFloat(attribute, 'f', -1, 64))
}
}
result[k] = *NewExample(classLabel, attributes)
}
return result, scanner.Err()
}
func main() {
example, err := readFile("test.txt")
fmt.Println(example, err)
}
When you do this inside the for loop:
buffer.Write(part)
newLine := buffer.String()
fmt.Println("newLine=" + newLine)
The next line gets appended to buffer.
That is,
before the loop begins, buffer contains 2 3,
and then after reading 1.0 2.0 3.0,
it gets appended to buffer,
so the content becomes 2 31.0 2.0 3.0,
which you store in newLine.
That's where things start to go sideways.
You probably want to clear the buffer before reading each new line:
buffer.Reset()
buffer.Write(part)
newLine := buffer.String()
fmt.Println("newLine=" + newLine)
But then you will have further problems still, here:
if scanner.Scan() {
classLabel, err = strconv.Atoi(scanner.Text())
if err != nil {
return result, err
}
}
Since the line contains 1.0 2.0 3.0, the strconf.Atoi is going to fail.
I don't understand the purpose of this snippet,
perhaps you can delete it (or comment out).
With the above fixed, you will still have one more problem, on this line:
attributes = append(attributes, attribute)
Since attributes is never reset, it keeps growing.
That is, after the first line, it will contain 1 2 3,
and after the second line it will contain 1 2 3 -1 -2 -3.
You could correct that by moving the declaration of attributes without the outer loop, like this:
var attributes []float64
for i := 0; i < attributeNum; i++ {
var attribute float64
if scanner.Scan() {
attribute, err = strconv.ParseFloat(scanner.Text(), 64)
if err != nil {
return result, err
}
attributes = append(attributes, attribute)
fmt.Println("attribute=" + strconv.FormatFloat(attribute, 'f', -1, 64))
}
}