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  }