github.com/bingoohuang/gg@v0.0.0-20240325092523-45da7dee9335/pkg/randx/img.go (about) 1 package randx 2 3 import ( 4 "bytes" 5 "fmt" 6 "image" 7 "image/color" 8 "image/draw" 9 "image/jpeg" 10 "image/png" 11 "os" 12 "path/filepath" 13 "strings" 14 15 "github.com/bingoohuang/gg/pkg/man" 16 "github.com/bingoohuang/gg/pkg/ss" 17 "github.com/pbnjay/pixfont" 18 ) 19 20 type RandomImageResult struct { 21 Size int 22 Filename string 23 } 24 25 // RandomImage creates a random image. 26 // Environment variables supported: 27 // GG_IMG_FAST=Y/N to enable fast mode or not 28 // GG_IMG_FORMAT=jpg/png to choose the format 29 // GG_IMG_FILE_SIZE=10M to set image file size 30 // GG_IMG_SIZE=640x320 to set the {width}x{height} of image 31 func RandomImage(prefix string) (*RandomImageResult, error) { 32 imgFormat := parseImageFormat("GG_IMG_FORMAT") 33 width, height := parseImageSize("GG_IMG_SIZE") 34 fn := fmt.Sprintf("%s_%dx%d%s", prefix, width, height, imgFormat) 35 c := ImgConfig{ 36 Width: width, 37 Height: height, 38 RandomText: fmt.Sprintf("%s", prefix), 39 FastMode: parseImageFastMode("GG_IMG_FAST"), 40 } 41 size := c.GenFile(fn, int(parseImageFileSize("GG_IMG_FILE_SIZE"))) 42 return &RandomImageResult{Size: size, Filename: fn}, nil 43 } 44 45 func parseImageFastMode(envName string) bool { 46 if val := os.Getenv(envName); val != "" { 47 if v, err := ss.ParseBoolE(val); err == nil { 48 return v 49 } 50 } 51 52 return true 53 } 54 55 func parseImageFileSize(envName string) (v uint64) { 56 if val := os.Getenv(envName); val != "" { 57 v, _ = man.ParseBytes(val) 58 } 59 return v 60 } 61 62 func parseImageFormat(envName string) string { 63 if v := os.Getenv(envName); v != "" { 64 switch strings.ToLower(v) { 65 case ".jpg", "jpg", ".jpeg", "jpeg": 66 return ".jpg" 67 case ".png", "png": 68 return ".png" 69 } 70 } 71 return ss.If(Bool(), ".jpg", ".png") 72 } 73 74 func parseImageSize(envName string) (width, height int) { 75 width, height = 640, 320 76 if val := os.Getenv(envName); val != "" { 77 val = strings.ToLower(val) 78 parts := strings.SplitN(val, "x", 2) 79 if len(parts) == 2 { 80 if v := ss.ParseInt(parts[0]); v > 0 { 81 width = v 82 } 83 if v := ss.ParseInt(parts[1]); v > 0 { 84 height = v 85 } 86 } 87 } 88 return width, height 89 } 90 91 // GenerateRandomImageFile generate image file. 92 // If fastMode is true, a sparse file is filled with zero (ascii NUL) and doesn't actually take up the disk space 93 // until it is written to, but reads correctly. 94 // $ ls -lh 424661641.png 95 // -rw------- 1 bingoobjca staff 488K Mar 15 12:19 424661641.png 96 // $ du -hs 424661641.png 97 // 8.0K 424661641.png 98 // If fastMode is false, an actually sized file will generated. 99 // $ ls -lh 1563611881.png 100 // -rw------- 1 bingoobjca staff 488K Mar 15 12:28 1563611881.png 101 // $ du -hs 1563611881.png 102 // 492K 1563611881.png 103 104 type ImgConfig struct { 105 Width int 106 Height int 107 RandomText string 108 FastMode bool 109 PixelSize int 110 } 111 112 func (c *ImgConfig) GenFile(filename string, fileSize int) int { 113 f, _ := os.OpenFile(filename, os.O_WRONLY|os.O_CREATE, 0o600) 114 defer f.Close() 115 116 data, imgSize := c.Gen(filepath.Ext(filename)) 117 f.Write(data) 118 if fileSize <= imgSize { 119 return imgSize 120 } 121 122 if !c.FastMode { 123 b := Bytes(fileSize - imgSize) 124 f.Write(b) 125 return fileSize 126 } 127 128 // refer to https://stackoverflow.com/questions/16797380/how-to-create-a-10mb-file-filled-with-000000-data-in-golang 129 // use f.Truncate to change size of the file 130 // If you are using unix, then you can create a sparse file very quickly. 131 // A sparse file is filled with zero (ascii NUL) and doesn't actually take up the disk space 132 // until it is written to, but reads correctly. 133 f.Truncate(int64(fileSize)) 134 return fileSize 135 } 136 137 // Gen generate a random image with imageFormat (jpg/png) . 138 // refer: https://onlinejpgtools.com/generate-random-jpg 139 func (c *ImgConfig) Gen(imageFormat string) ([]byte, int) { 140 var img draw.Image 141 142 format := strings.ToLower(imageFormat) 143 switch format { 144 case ".jpg", ".jpeg": 145 img = image.NewNRGBA(image.Rect(0, 0, c.Width, c.Height)) 146 default: // png 147 img = image.NewRGBA(image.Rect(0, 0, c.Width, c.Height)) 148 } 149 150 if c.PixelSize == 0 { 151 c.PixelSize = 40 152 } 153 154 yp := c.Height / c.PixelSize 155 xp := c.Width / c.PixelSize 156 for yi := 0; yi < yp; yi++ { 157 for xi := 0; xi < xp; xi++ { 158 drawPixelWithColor(img, yi, xi, c.PixelSize, Color()) 159 } 160 } 161 162 if c.RandomText != "" { 163 pixfont.DrawString(img, 10, 10, c.RandomText, color.Black) 164 } 165 166 var buf bytes.Buffer 167 switch format { 168 case ".jpg", ".jpeg": 169 jpeg.Encode(&buf, img, &jpeg.Options{Quality: 100}) // 图像质量值为100,是最好的图像显示 170 default: // png 171 png.Encode(&buf, img) 172 } 173 174 return buf.Bytes(), buf.Len() 175 } 176 177 // drawPixelWithColor draw pixels on img from yi, xi and randomColor with size of pixelSize x pixelSize 178 func drawPixelWithColor(img draw.Image, yi, xi, pixelSize int, c color.Color) { 179 ys := yi * pixelSize 180 ym := ys + pixelSize 181 xs := xi * pixelSize 182 xm := xs + pixelSize 183 184 for y := ys; y < ym; y++ { 185 for x := xs; x < xm; x++ { 186 img.Set(x, y, c) 187 } 188 } 189 } 190 191 // Color generate a random color 192 func Color() color.Color { 193 return color.RGBA{R: uint8(IntN(255)), G: uint8(IntN(255)), B: uint8(IntN(255)), A: uint8(IntN(255))} 194 }