├── example ├── out.jpg ├── timg.jpg ├── FZHTJW.TTF ├── timg1.jpg └── main.go ├── text2pic.go ├── README.md └── lines.go /example/out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqbobo/text2pic/HEAD/example/out.jpg -------------------------------------------------------------------------------- /example/timg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqbobo/text2pic/HEAD/example/timg.jpg -------------------------------------------------------------------------------- /example/FZHTJW.TTF: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqbobo/text2pic/HEAD/example/FZHTJW.TTF -------------------------------------------------------------------------------- /example/timg1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hqbobo/text2pic/HEAD/example/timg1.jpg -------------------------------------------------------------------------------- /text2pic.go: -------------------------------------------------------------------------------- 1 | package text2pic 2 | 3 | import ( 4 | "fmt" 5 | "github.com/golang/freetype/truetype" 6 | "golang.org/x/image/math/fixed" 7 | "image" 8 | "image/color" 9 | "image/draw" 10 | "image/jpeg" 11 | "image/png" 12 | "io" 13 | ) 14 | 15 | type Color image.Image 16 | 17 | var ( 18 | ColorRed = image.NewUniform(color.RGBA{0xFF, 0x00, 0x00, 0xff}) 19 | ColorGreen = image.NewUniform(color.RGBA{0x00, 0xFF, 0x00, 0xff}) 20 | ColorBlue = image.NewUniform(color.RGBA{0x00, 0x00, 0xFF, 0xff}) 21 | ColorWhite = image.White 22 | ColorBlack = image.Black 23 | TypePng = 1 24 | TypeJpeg = 2 25 | ) 26 | 27 | type Padding struct { 28 | Left int 29 | Right int 30 | Bottom int 31 | Top int 32 | LineSpace int 33 | } 34 | 35 | type Configure struct { 36 | Width int 37 | BgColor Color 38 | } 39 | 40 | func NewTextPicture(conf Configure) *TextPicture { 41 | pic := new(TextPicture) 42 | pic.conf = conf 43 | return pic 44 | } 45 | 46 | type TextPicture struct { 47 | text string 48 | conf Configure 49 | lines []line 50 | } 51 | 52 | func (this *TextPicture) AddTextLine(text string, fontSize float64, font *truetype.Font, color Color, padding Padding) { 53 | textline := new(textLine) 54 | textline.font = font 55 | textline.fontsize = fontSize 56 | textline.text = text 57 | textline.color = color 58 | textline.padding = padding 59 | this.lines = append(this.lines, textline) 60 | } 61 | 62 | func (this *TextPicture) AddPictureLine(reader io.Reader, padding Padding) { 63 | picline := new(pictureLine) 64 | picline.reader = reader 65 | picline.padding = padding 66 | this.lines = append(this.lines, picline) 67 | } 68 | 69 | func (this *TextPicture) Draw(writer io.Writer, filetype int) error { 70 | var err error 71 | // Initialize the context. 72 | height := 0 73 | width := this.conf.Width 74 | rgba := image.NewRGBA(image.Rect(0, 0, width, height)) 75 | for _, v := range this.lines { 76 | height += v.getHeight(width, rgba) 77 | } 78 | rgba = image.NewRGBA(image.Rect(0, 0, width, height)) 79 | draw.Draw(rgba, rgba.Bounds(), this.conf.BgColor, image.ZP, draw.Src) 80 | pt := fixed.Point26_6{X: fixed.Int26_6(0), Y: fixed.Int26_6(0)} 81 | for _, v := range this.lines { 82 | if e := v.draw(this.conf.Width, &pt, rgba); e != nil { 83 | fmt.Println("draw error :", e) 84 | } 85 | } 86 | if filetype == TypePng { 87 | err = png.Encode(writer, rgba) 88 | if err != nil { 89 | return err 90 | 91 | } 92 | } else { 93 | err = jpeg.Encode(writer, rgba, nil) 94 | if err != nil { 95 | return err 96 | 97 | } 98 | } 99 | return nil 100 | } 101 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "github.com/hqbobo/text2pic" 6 | "github.com/golang/freetype" 7 | "io/ioutil" 8 | "log" 9 | "os" 10 | "bufio" 11 | 12 | ) 13 | 14 | func main() { 15 | 16 | // Read the font data. 17 | fontBytes, err := ioutil.ReadFile("FZHTJW.ttf") 18 | if err != nil { 19 | log.Println(err) 20 | return 21 | } 22 | //produce the fonttype 23 | f, err := freetype.ParseFont(fontBytes) 24 | if err != nil { 25 | log.Println(err) 26 | return 27 | } 28 | 29 | //define New picture with given width in px 30 | //the height will be calucated before draw on picture 31 | //picture will be resize to 80% of the width you given 32 | pic := text2pic.NewTextPicture(text2pic.Configure{Width: 1080, BgColor:text2pic.ColorWhite}) 33 | 34 | //add chinese line 35 | pic.AddTextLine("1.哈哈哈哈哈哈哈", 30, f, text2pic.ColorGreen, text2pic.Padding{Left: 20, Top: 10, Bottom: 0}) 36 | pic.AddTextLine(" 北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部", 10, f, text2pic.ColorBlue, text2pic.Padding{LineSpace:5, Top:0, Left: 20, Right: 20, Bottom: 0}) 37 | //add picture 38 | file, err := os.Open("timg.jpg") 39 | if err != nil { 40 | fmt.Println(err) 41 | } 42 | defer file.Close() 43 | 44 | //pic.AddPictureLine(file, text2pic.Padding{Bottom: 60}) 45 | 46 | // 47 | //////add full english text 48 | pic.AddTextLine("3.For English", 30, f, text2pic.ColorRed, text2pic.Padding{Bottom: 0}) 49 | pic.AddTextLine(" The Turkish lira plunged as much as 11% against the dollar, hitting a record low, before recovering some of its losses in volatile trading. The lira had already plummeted more than 20% last week as a political clash with the United States intensified and investors fretted about the Turkish government's lack of action to tackle the problems plaguing its economy. ", 12, f, text2pic.ColorBlue, text2pic.Padding{Left: 20, Right: 20, Bottom: 0}) 50 | pic.AddTextLine(" 北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部", 10, f, text2pic.ColorBlack, text2pic.Padding{LineSpace:5, Top:0, Left: 20, Right: 20, Bottom: 0}) 51 | 52 | // Save the output to file 53 | outFile, err := os.Create("out.jpg") 54 | if err != nil { 55 | return 56 | } 57 | defer outFile.Close() 58 | b := bufio.NewWriter(outFile) 59 | //produce the output 60 | pic.Draw(b, text2pic.TypeJpeg) 61 | e := b.Flush() 62 | if e!=nil { 63 | fmt.Println(e) 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # text2pic 3 | 4 | ## Description 5 | 6 | >Convert text into pictures, this is designed for posting long text message to weibo initially. 7 | 8 | 1. paint text on picture. 9 | 2. support jpg and png as well 10 | 11 | ## Functions 12 | 13 | > Create new picture with Configure 14 | 15 | `pic := text2pic.NewTextPicture(text2pic.Configure{Width: 720, BgColor:text2pic.ColorWhite})` 16 | 17 | > Add text line to picture with font, color and padding 18 | 19 | `pic.AddTextLine(" The Turkish lira plunged as much as 11% against the dollar", 13, f, text2pic.ColorBlue, text2pic.Padding{Left: 20, Right: 20, Bottom: 30})` 20 | 21 | > Add picture io.reader is required and padding as well 22 | 23 | `pic.AddPictureLine(file, text2pic.Padding{Bottom: 20})` 24 | 25 | > Draw it on io.writer. TypePng and TypeJpeg are supported 26 | 27 | `pic.Draw(writer, text2pic.TypeJpeg)` 28 | 29 | ## Example 30 | > [Source Code](https://github.com/hqbobo/text2pic/tree/master/example) see in the example directory 31 | ![output.jpg](https://github.com/hqbobo/text2pic/blob/master/example/out.jpg) 32 | ``` 33 | package main 34 | 35 | import ( 36 | "fmt" 37 | "github.com/golang/freetype" 38 | "github.com/hqbobo/text2pic" 39 | "io/ioutil" 40 | "log" 41 | "os" 42 | "bufio" 43 | ) 44 | 45 | func main() { 46 | 47 | // Read the font data. 48 | fontBytes, err := ioutil.ReadFile("FZHTJW.TTF") 49 | if err != nil { 50 | log.Println(err) 51 | return 52 | } 53 | //produce the fonttype 54 | f, err := freetype.ParseFont(fontBytes) 55 | if err != nil { 56 | log.Println(err) 57 | return 58 | } 59 | 60 | //define New picture with given width in px 61 | //the height will be calucated before draw on picture 62 | //picture will be resize to 80% of the width you given 63 | pic := text2pic.NewTextPicture(text2pic.Configure{ 64 | Width: 720, 65 | }) 66 | 67 | //add chinese line 68 | pic.AddTextLine("1.这个是标题", 20, f, text2pic.ColorRed, text2pic.Padding{Left: 20, Top: 10, Bottom: 20}) 69 | pic.AddTextLine(" 北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称北京铁路局今天凌晨2时16分发布消息称:8月12日23时04分,aaaa京沪高铁廊坊至北京aaaaa南间发生设备故障,导致部分列车晚点。铁路部门及时启动应急预案处置时16分发布消息称时16分发布消息称", 12, f, text2pic.ColorGreen, text2pic.Padding{Left: 20, Right: 20, Bottom: 30}) 70 | //add picture 71 | file, err := os.Open("timg.jpg") 72 | if err != nil { 73 | fmt.Println(err) 74 | } 75 | defer file.Close() 76 | 77 | pic.AddPictureLine(file, text2pic.Padding{Bottom: 20}) 78 | 79 | //add full english text 80 | pic.AddTextLine("3.For English", 20, f, text2pic.ColorRed, text2pic.Padding{Bottom: 20}) 81 | pic.AddTextLine(" The Turkish lira plunged as much as 11% against the dollar, hitting a record low, before recovering some of its losses in volatile trading. The lira had already plummeted more than 20% last week as a political clash with the United States intensified and investors fretted about the Turkish government's lack of action to tackle the problems plaguing its economy. ", 13, f, text2pic.ColorBlue, text2pic.Padding{Left: 20, Right: 20, Bottom: 30}) 82 | 83 | // Save the output to file 84 | outFile, err := os.Create("out.jpg") 85 | if err != nil { 86 | return 87 | } 88 | defer outFile.Close() 89 | b := bufio.NewWriter(outFile) 90 | //produce the output 91 | pic.Draw(b, text2pic.TypeJpeg) 92 | e := b.Flush() 93 | if e!=nil { 94 | fmt.Println(e) 95 | } 96 | 97 | } 98 | ``` -------------------------------------------------------------------------------- /lines.go: -------------------------------------------------------------------------------- 1 | package text2pic 2 | 3 | import ( 4 | "flag" 5 | "golang.org/x/image/math/fixed" 6 | "github.com/golang/freetype" 7 | "github.com/golang/freetype/truetype" 8 | "golang.org/x/image/font" 9 | "image" 10 | "image/draw" 11 | "fmt" 12 | "image/jpeg" 13 | "io" 14 | "github.com/nfnt/resize" 15 | "image/png" 16 | "errors" 17 | "bufio" 18 | "bytes" 19 | ) 20 | var ( 21 | dpi = flag.Float64("dpi", 288, "screen resolution in Dots Per Inch") 22 | hinting = flag.String("hinting", "none", "none | full") 23 | size = flag.Float64("size", 8, "font size in points") 24 | spacing = flag.Float64("spacing", 1.5, "line spacing (e.g. 2 means double spaced)") 25 | textpadding = 25 26 | ) 27 | 28 | type line interface { 29 | draw(width int, pt *fixed.Point26_6, image draw.Image) error 30 | getHeight(width int, image draw.Image) int 31 | } 32 | 33 | type textLine struct { 34 | fontsize float64 35 | text string 36 | font *truetype.Font 37 | padding Padding 38 | color image.Image 39 | lines int 40 | } 41 | 42 | func (this *textLine) draw(width int, pt *fixed.Point26_6, image draw.Image) error { 43 | c := freetype.NewContext() 44 | c.SetDPI(*dpi) 45 | c.SetFont(this.font) 46 | c.SetFontSize(float64(this.fontsize)) 47 | c.SetClip(image.Bounds()) 48 | c.SetDst(image) 49 | c.SetSrc(this.color) 50 | 51 | //switch *hinting { 52 | //default: 53 | c.SetHinting(font.HintingFull) 54 | //case "full": 55 | // c.SetHinting(font.HintingFull) 56 | //} 57 | // Draw the guidelines. 58 | //for i := 0; i < 200; i++ { 59 | // image.Set(10, 10+i, ruler) 60 | // image.Set(10+i, 10, ruler) 61 | //} 62 | 63 | //line define adjust padding 64 | //ajust line space 65 | width -= this.padding.Right 66 | toppadding := freetype.Pt(0, this.padding.Top) 67 | pts := freetype.Pt(this.padding.Left, this.padding.LineSpace+int(c.PointToFixed(this.fontsize)>>6)) 68 | pt.X = pts.X 69 | pt.Y = pt.Y + pts.Y + toppadding.Y //add paddingtop 70 | index := 0 71 | lastwidth := 0 72 | lc := 1 73 | for i := 1 ; i <= len(this.text); i++ { 74 | 75 | //test the text length 76 | //fmt.Println("i[", i ,"]len:",len(this.text),"-",string([]byte(this.text)[index:i])) 77 | //fmt.Println([]byte(this.text)[index:i]) 78 | 79 | rpts , err := c.DrawString(string([]byte(this.text)[index:i]), *pt) 80 | if err != nil { 81 | return err 82 | } 83 | //fmt.Println(rpts.X.Floor() ,":", width,":",lastwidth, "[",[]byte(this.text)[i-1],"]") 84 | 85 | //english char 86 | if []byte(this.text)[i-1] >33 && []byte(this.text)[i-1] < 127 && (rpts.X.Floor() + int(c.PointToFixed(this.fontsize)>>6) > width) { 87 | index = i 88 | pt.Y = pt.Y + pts.Y 89 | lc++ 90 | continue 91 | } 92 | //other encoding 93 | if (rpts.X.Floor() + int(c.PointToFixed(this.fontsize)>>6) > width) && (lastwidth > width) { 94 | _ , err := c.DrawString(string([]byte(this.text)[index:i-1]), *pt) 95 | if err != nil { 96 | return err 97 | } 98 | index = i 99 | pt.Y = pt.Y + pts.Y 100 | lc++ 101 | } 102 | lastwidth = rpts.X.Floor() 103 | } 104 | //add extraline 105 | for ; this.lines - lc > 0; lc++ { 106 | pt.Y = pt.Y + pts.Y 107 | } 108 | 109 | //add buttom padding 110 | padding := freetype.Pt(0, 0+this.padding.Bottom + textpadding) 111 | pt.Y = pt.Y + padding.Y 112 | return nil 113 | } 114 | 115 | func (this *textLine) getHeight(width int, image draw.Image) int { 116 | c := freetype.NewContext() 117 | c.SetDPI(*dpi) 118 | c.SetFont(this.font) 119 | c.SetFontSize(float64(this.fontsize)) 120 | c.SetClip(image.Bounds()) 121 | c.SetDst(image) 122 | c.SetHinting(font.HintingFull) 123 | pt := freetype.Pt(0, 0) 124 | rpt , err := c.DrawString(this.text, pt) 125 | if err != nil { 126 | return 0 127 | } 128 | //calculate lines count 129 | lines := (rpt.X.Floor() / (width - this.padding.Left - this.padding.Right)) 130 | //check the left text 131 | if rpt.X.Floor() %width > 0 { 132 | lines++ 133 | } 134 | 135 | //add extraline avoid missing char 136 | if lines >=5 { 137 | lines++ 138 | } 139 | this.lines = lines 140 | //add padding 141 | return int(c.PointToFixed(this.fontsize)>>6) *lines + lines *this.padding.LineSpace + this.padding.Bottom + this.padding.Top + textpadding 142 | } 143 | 144 | type pictureLine struct { 145 | reader io.Reader 146 | padding Padding 147 | img image.Image 148 | } 149 | 150 | func (this *pictureLine) draw(width int, pt *fixed.Point26_6, img draw.Image) error { 151 | if this.img == nil { 152 | return errors.New("err pic") 153 | } 154 | draw.Draw(img, this.img.Bounds().Add(image.Pt(width/10, pt.Y.Floor())) ,this.img, this.img.Bounds().Min ,draw.Src) 155 | //add padding 156 | padding := freetype.Pt(0, 0+this.padding.Bottom + this.img.Bounds().Max.Y) 157 | pt.Y = pt.Y + padding.Y 158 | return nil 159 | } 160 | 161 | func (this *pictureLine) getHeight(width int, image draw.Image) int { 162 | //try png and jpeg to decode 163 | reader := bufio.NewReader(this.reader) 164 | buf := make ([]byte, 10 * 1024 * 1024) 165 | n , e := reader.Read(buf) 166 | img, e := jpeg.Decode(bytes.NewReader(buf[0:n])) 167 | if e != nil { 168 | img, e = png.Decode(bytes.NewReader(buf[0:n])) 169 | if e != nil { 170 | fmt.Println("failed to parse image file") 171 | return 0 172 | } 173 | } 174 | // resize to 80% of the width using Lanczos resampling 175 | m := resize.Resize(uint(width/5*4), 0, img, resize.Lanczos3) 176 | this.img = m 177 | return m.Bounds().Size().Y + this.padding.Bottom + this.padding.Top 178 | } --------------------------------------------------------------------------------