├── LICENSE ├── README.md ├── fetching.go ├── fetching_test.go ├── imageio.go ├── imageio_test.go ├── images ├── camera.png ├── image720x720.jpg └── image720x720.png ├── utils.go └── utils_test.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 openatx 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # go-imageio 2 | > Golang wraps for image IO read and write. 3 | 4 | ### Usages 5 | 6 | ```go 7 | mp4 := NewMp4("test.mp4", &Options{}) 8 | for i := 0; i < 100; i++ { 9 | err := mp4.WriteImage("camera.png") 10 | if err != nil { 11 | log.Printf(err) 12 | } 13 | } 14 | ``` 15 | 16 | ### Result 17 | 2016-10-19 18 | > System: Win 7 19 | > Memory: 8G 20 | > CPU: Core(TM) i5-4570 3.20GHz 21 | 22 | ``` 23 | -- Write -- 24 | BenchmarkWriteJPEGImageFile 100 25.75 ms/op 25 | BenchmarkWritePNGImageFile 20 60.60 ms/op 26 | BenchmarkWriteJPEGImage 100 17.11 ms/op 27 | BenchmarkWritePNGImage 100 14.33 ms/op 28 | -- Decode -- 29 | BenchmarkDecodeJPEGImage 100 17.54 ms/op 30 | BenchmarkDecodePNGImage 20 57.90 ms/op 31 | ``` 32 | 33 | 2016-10-19 34 | > Raspberry 2 35 | 36 | ``` 37 | -- Write -- 38 | BenchmarkWriteJPEGImageFile 2 648 ms/op 39 | BenchmarkWritePNGImageFile 1 1271 ms/op 40 | BenchmarkWriteJPEGImage 3 436 ms/op 41 | BenchmarkWritePNGImage 3 400 ms/op 42 | -- Decode -- 43 | BenchmarkDecodeJPEGImage 2 510 ms/op 44 | BenchmarkDecodePNGImage 1 1229 ms/op 45 | ``` 46 | 47 | ### Reference 48 | 49 | - [ffmpeg](https://www.ffmpeg.org/) 50 | - [imaging](https://github.com/disintegration/imaging) 51 | 52 | ### LICENSE 53 | 54 | Under LICENSE [MIT](https://github.com/openatx/go-stf/blob/master/LICENSE) 55 | -------------------------------------------------------------------------------- /fetching.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "log" 7 | "net/http" 8 | "os" 9 | ) 10 | 11 | const ( 12 | RemoteResourceUrl = "https://github.com/imageio/imageio-binaries/raw/master/ffmpeg" 13 | ) 14 | 15 | var ( 16 | FnamePerPlatform = map[string]string{ 17 | "osx32": "ffmpeg.osx", 18 | "osx64": "ffmpeg.osx", 19 | "win32": "ffmpeg.win32.exe", 20 | "win64": "ffmpeg.win32.exe", 21 | "linux32": "ffmpeg.linux32", 22 | "linux64": "ffmpeg.linux64", 23 | } 24 | ) 25 | 26 | // The results from individual calls to it. 27 | type PassThru struct { 28 | io.Reader 29 | CurrentSize int // Current # of bytes transferred 30 | TotalSize int // Total # of bytes transferred 31 | } 32 | 33 | // Read 'overrides' the underlying io.Reader's Read method. 34 | // This is the one that will be called by io.Copy(). We simply 35 | // use it to keep track of byte counts and then forward the call. 36 | func (pt *PassThru) Read(p []byte) (int, error) { 37 | n, err := pt.Reader.Read(p) 38 | pt.CurrentSize += n 39 | if err == nil { 40 | totalNum := pt.TotalSize / n 41 | currentNum := pt.CurrentSize / n 42 | if currentNum != 0 { 43 | fmt.Printf("\r Downloading: %.0f%%", float64(currentNum) / float64(totalNum) * 100) 44 | if float64(currentNum) / float64(totalNum) == 1.0 { 45 | fmt.Println() 46 | } 47 | os.Stdout.Sync() 48 | } 49 | } 50 | return n, err 51 | } 52 | 53 | // Get a filename for the local version of a file from the web 54 | func GetRomoteFile(fname string) error { 55 | url := RemoteResourceUrl + "/" + FnamePerPlatform[fname] 56 | return downloadFromUrl(url, FnamePerPlatform[fname]) 57 | } 58 | 59 | // Load requested file, downloading it if needed or requested. 60 | func downloadFromUrl(url string, filename string) error { 61 | log.Println("FFmpeg was not found on your computer; downloading it now from", url) 62 | tmpFilename := filename + ".cache" 63 | output, err := os.Create(tmpFilename) 64 | if err != nil { 65 | return err 66 | } 67 | response, err := http.Get(url) 68 | fileSize := response.ContentLength 69 | log.Printf("Total file size : %v bytes\n", fileSize) 70 | if err != nil { 71 | return err 72 | } 73 | defer response.Body.Close() 74 | src := &PassThru{Reader: response.Body, TotalSize: int(fileSize)} 75 | count, err := io.Copy(output, src) 76 | if err != nil { 77 | return err 78 | } 79 | if err := output.Close(); err != nil { 80 | return err 81 | } 82 | if count == fileSize { 83 | fmt.Println("Transferred", count, "bytes") 84 | err := os.Rename(tmpFilename, filename) 85 | if err != nil { 86 | return err 87 | } 88 | } 89 | return nil 90 | } 91 | -------------------------------------------------------------------------------- /fetching_test.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestGetRomoteFile(t *testing.T) { 9 | plat := GetPlatform() 10 | err := GetRomoteFile(FnamePerPlatform[plat]) 11 | if err != nil { 12 | log.Fatal(err) 13 | } 14 | log.Printf("Done.") 15 | } 16 | -------------------------------------------------------------------------------- /imageio.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "errors" 5 | "image" 6 | "io" 7 | "log" 8 | "os" 9 | "os/exec" 10 | "strconv" 11 | "fmt" 12 | ) 13 | 14 | // Mp4 video convert options. 15 | type Options struct { 16 | FPS int 17 | Codec string 18 | Pixelformat string 19 | Pixfmt string 20 | } 21 | 22 | // Video struct. 23 | type Video struct { 24 | Cmd *exec.Cmd 25 | StdinWr io.WriteCloser 26 | Dimension string 27 | ExePath string 28 | Output string 29 | Option *Options 30 | } 31 | 32 | // New Instance. 33 | func NewVideo(output string, op *Options) *Video { 34 | if op.Codec == "" { 35 | op.Codec = "libx264" 36 | } 37 | if op.Pixelformat == "" { 38 | op.Pixelformat = "yuv420p" 39 | } 40 | if op.Pixfmt == "" { 41 | op.Pixfmt = "rgba" 42 | } 43 | if op.FPS == 0 { 44 | op.FPS = 25 45 | } 46 | return &Video{Cmd: nil, StdinWr: nil, Dimension: "", Output: output, Option: op} 47 | } 48 | 49 | // Initialize FFmpeg thread. 50 | func (this *Video) initialize() error { 51 | exe, err := GetFFmpegExe() 52 | if err != nil { 53 | return err 54 | } 55 | if this.Dimension == "" { 56 | this.Dimension = "512x512" 57 | } 58 | pix_fmt := this.Option.Pixfmt 59 | fps := strconv.Itoa(this.Option.FPS) 60 | codec := this.Option.Codec 61 | pixelformat := this.Option.Pixelformat 62 | outputfile := this.Output 63 | 64 | cmdstr := []string{"-y", 65 | "-f", "rawvideo", 66 | "-vcodec", "rawvideo", 67 | "-s", this.Dimension, 68 | "-pix_fmt", pix_fmt, 69 | "-r", fps, 70 | "-i", "-", "-an", 71 | "-vcodec", codec, 72 | "-pix_fmt", pixelformat, 73 | "-crf", "25", 74 | "-r", "50", 75 | "-v", "warning", outputfile} 76 | return this.execFFmpegCommands(exe, cmdstr) 77 | } 78 | 79 | // Write image by file path. 80 | func (this *Video) WriteImageFile(imagePath string) error { 81 | img, err := LoadImage(imagePath) 82 | if err != nil { 83 | return err 84 | } 85 | width, height, err := this.getImageDimension(imagePath) 86 | if err != nil { 87 | return err 88 | } 89 | dimension := fmt.Sprintf("%dx%d", width, height) 90 | if this.Dimension == "" { 91 | this.Dimension = dimension 92 | if err := this.initialize(); err != nil { 93 | return err 94 | } 95 | } 96 | if dimension != this.Dimension { 97 | return errors.New("All images in a movie should have same size.") 98 | } 99 | if img != nil && this.Cmd != nil && this.StdinWr != nil { 100 | imgstring := LoadImageBitmap(img) 101 | this.StdinWr.Write(imgstring) 102 | } 103 | return nil 104 | } 105 | 106 | // Write image by image.Image 107 | func (this *Video) WriteImage(image image.Image) error { 108 | width := image.Bounds().Size().X 109 | height := image.Bounds().Size().Y 110 | dimension := fmt.Sprintf("%dx%d", width, height) 111 | if this.Dimension == "" { 112 | this.Dimension = dimension 113 | if err := this.initialize(); err != nil { 114 | return err 115 | } 116 | } 117 | if dimension != this.Dimension { 118 | return errors.New("All images in a movie should have same size.") 119 | } 120 | if image != nil && this.Cmd != nil && this.StdinWr != nil { 121 | imagestring := LoadImageBitmap(image) 122 | _, err := this.StdinWr.Write(imagestring) 123 | if err != nil { 124 | return err 125 | } 126 | } 127 | return nil 128 | } 129 | 130 | // Get image Dimension 131 | func (this *Video) getImageDimension(imagePath string) (int, int, error) { 132 | file, err := os.Open(imagePath) 133 | if err != nil { 134 | return 0, 0, err 135 | } 136 | image, _, err := image.DecodeConfig(file) 137 | return image.Width, image.Height, err 138 | } 139 | 140 | // Close ffmpeg 141 | func (this *Video) Close() error { 142 | if this.Cmd == nil { 143 | return errors.New("FFmpeg command is nil.") 144 | } 145 | if this.StdinWr != nil { 146 | err := this.StdinWr.Close() 147 | if err != nil { 148 | return err 149 | } 150 | log.Print("Close the mp4 video.") 151 | } 152 | this.Cmd.Wait() 153 | this.Cmd = nil 154 | return nil 155 | } 156 | 157 | // Execute FFmpeg commands. 158 | func (this *Video) execFFmpegCommands(commandName string, params []string) error { 159 | this.Cmd = exec.Command(commandName, params...) 160 | stdinWr, err := this.Cmd.StdinPipe() 161 | if err != nil { 162 | return err 163 | } 164 | this.StdinWr = stdinWr 165 | this.Cmd.Start() 166 | return nil 167 | } 168 | -------------------------------------------------------------------------------- /imageio_test.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "testing" 5 | "fmt" 6 | ) 7 | 8 | var mp4 = NewVideo("test.mp4", &Options{FPS:24}) 9 | 10 | var imgjpg, _ = LoadImage("images/image720x720.jpg") 11 | 12 | var imgpng, _ = LoadImage("images/image720x720.png") 13 | 14 | func BenchmarkWriteJPEGImageFile(b *testing.B) { 15 | for i := 0; i < b.N; i++ { 16 | mp4.WriteImageFile("images/image720x720.jpg") 17 | } 18 | } 19 | 20 | func BenchmarkWritePNGImageFile(b *testing.B) { 21 | for i := 0; i < b.N; i++ { 22 | mp4.WriteImageFile("images/image720x720.png") 23 | } 24 | } 25 | 26 | func BenchmarkWriteJPEGImage(b *testing.B) { 27 | for i := 0; i < b.N; i++ { 28 | mp4.WriteImage(imgjpg) 29 | } 30 | } 31 | 32 | func BenchmarkWritePNGImage(b *testing.B) { 33 | for i := 0; i < b.N; i++ { 34 | mp4.WriteImage(imgpng) 35 | } 36 | } 37 | 38 | func BenchmarkDecodeJPEGImage(b *testing.B) { 39 | for i := 0; i < b.N; i++ { 40 | _, err := LoadImage("images/image720x720.jpg") 41 | if err != nil { 42 | fmt.Println(err) 43 | } 44 | } 45 | } 46 | 47 | func BenchmarkDecodePNGImage(b *testing.B) { 48 | for i := 0; i < b.N; i++ { 49 | _, err := LoadImage("images/image720x720.png") 50 | if err != nil { 51 | fmt.Println(err) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /images/camera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openatx-archive/go-imageio/4aaf21e3918580d7b70e2758830d9347c2603791/images/camera.png -------------------------------------------------------------------------------- /images/image720x720.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openatx-archive/go-imageio/4aaf21e3918580d7b70e2758830d9347c2603791/images/image720x720.jpg -------------------------------------------------------------------------------- /images/image720x720.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openatx-archive/go-imageio/4aaf21e3918580d7b70e2758830d9347c2603791/images/image720x720.png -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "bufio" 5 | "errors" 6 | "image" 7 | "image/color" 8 | "os" 9 | "os/exec" 10 | "runtime" 11 | "strconv" 12 | "strings" 13 | "sync" 14 | "sync/atomic" 15 | "path/filepath" 16 | "image/png" 17 | "image/jpeg" 18 | "image/gif" 19 | ) 20 | 21 | // Ensure we have our version of the binary freeimage lib. 22 | func GetFFmpegExe() (string, error) { 23 | inPath, err := CheckIfFFmpegInPATH() 24 | if inPath && err == nil { 25 | return "ffmpeg", err 26 | } 27 | plat := GetPlatform() 28 | if localFile, ok := FnamePerPlatform[plat]; ok { 29 | if _, err := os.Stat(localFile); os.IsNotExist(err) { 30 | return localFile, GetRomoteFile(plat) 31 | } else { 32 | isExe, err := CheckIfFileExecutable(localFile) 33 | if isExe && err == nil { 34 | return localFile, err 35 | } else { 36 | return localFile, GetRomoteFile(plat) 37 | } 38 | } 39 | } 40 | return "", errors.New("Platform not exist") 41 | } 42 | 43 | // Check if ffmpeg is in System PATH 44 | func CheckIfFFmpegInPATH() (bool, error) { 45 | return CheckFFmpegVersion("ffmpeg") 46 | } 47 | 48 | // Check if the exe file is excutable. 49 | func CheckIfFileExecutable(filepath string) (bool, error) { 50 | return CheckFFmpegVersion(filepath) 51 | } 52 | 53 | func CheckFFmpegVersion(filepath string) (bool, error) { 54 | cmd := exec.Command(filepath, "-version") 55 | stdout, err := cmd.StdoutPipe() 56 | if err != nil { 57 | return false, err 58 | } 59 | defer stdout.Close() 60 | cmd.Start() 61 | reader := bufio.NewReader(stdout) 62 | line, err := reader.ReadString('\n') 63 | if err != nil { 64 | return false, err 65 | } 66 | if strings.Contains(line, "ffmpeg") { 67 | return true, nil 68 | } else { 69 | return false, errors.New("Local file is not excutable.") 70 | } 71 | return true, nil 72 | } 73 | 74 | // Get a string that specifies the platform more specific than 75 | // The result can be: linux32, linux64, win32, 76 | // win64, osx32, osx64. Other platforms may be added in the future. 77 | func GetPlatform() (platform string) { 78 | bitNum := 32 << uintptr(^uintptr(0) >> 63) 79 | switch runtime.GOOS { 80 | case "darwin": 81 | platform = "osx" + strconv.Itoa(bitNum) 82 | break 83 | case "windows": 84 | platform = "win" + strconv.Itoa(bitNum) 85 | break 86 | case "linux": 87 | platform = "linux" + strconv.Itoa(bitNum) 88 | break 89 | } 90 | return platform 91 | } 92 | 93 | // Load image. 94 | func LoadImage(path string) (image.Image, error) { 95 | file, err := os.Open(path) 96 | if err != nil { 97 | return nil, err 98 | } 99 | defer file.Close() 100 | switch filepath.Ext(path) { 101 | case ".png": 102 | return png.Decode(file) 103 | case ".jpg": 104 | return jpeg.Decode(file) 105 | case ".gif": 106 | return gif.Decode(file) 107 | default: 108 | return nil, errors.New("Unkown file format.") 109 | } 110 | return jpeg.Decode(file) 111 | } 112 | 113 | // Load Image Bitmap. 114 | func LoadImageBitmap(imgfile image.Image) []uint8 { 115 | srcBounds := imgfile.Bounds() 116 | srcMinX := srcBounds.Min.X 117 | srcMinY := srcBounds.Min.Y 118 | 119 | dstBounds := srcBounds.Sub(srcBounds.Min) 120 | dstW := dstBounds.Dx() 121 | dstH := dstBounds.Dy() 122 | dst := image.NewNRGBA(dstBounds) 123 | 124 | switch src := imgfile.(type) { 125 | case *image.NRGBA: 126 | rowSize := srcBounds.Dx() * 4 127 | parallel(dstH, func(partStart, partEnd int) { 128 | for dstY := partStart; dstY < partEnd; dstY++ { 129 | di := dst.PixOffset(0, dstY) 130 | si := src.PixOffset(srcMinX, srcMinY + dstY) 131 | copy(dst.Pix[di:di + rowSize], src.Pix[si:si + rowSize]) 132 | } 133 | }) 134 | case *image.NRGBA64: 135 | parallel(dstH, func(partStart, partEnd int) { 136 | for dstY := partStart; dstY < partEnd; dstY++ { 137 | di := dst.PixOffset(0, dstY) 138 | si := src.PixOffset(srcMinX, srcMinY + dstY) 139 | for dstX := 0; dstX < dstW; dstX++ { 140 | 141 | dst.Pix[di + 0] = src.Pix[si + 0] 142 | dst.Pix[di + 1] = src.Pix[si + 2] 143 | dst.Pix[di + 2] = src.Pix[si + 4] 144 | dst.Pix[di + 3] = src.Pix[si + 6] 145 | 146 | di += 4 147 | si += 8 148 | 149 | } 150 | } 151 | }) 152 | case *image.RGBA: 153 | parallel(dstH, func(partStart, partEnd int) { 154 | for dstY := partStart; dstY < partEnd; dstY++ { 155 | di := dst.PixOffset(0, dstY) 156 | si := src.PixOffset(srcMinX, srcMinY + dstY) 157 | for dstX := 0; dstX < dstW; dstX++ { 158 | 159 | a := src.Pix[si + 3] 160 | dst.Pix[di + 3] = a 161 | switch a { 162 | case 0: 163 | dst.Pix[di + 0] = 0 164 | dst.Pix[di + 1] = 0 165 | dst.Pix[di + 2] = 0 166 | case 0xff: 167 | dst.Pix[di + 0] = src.Pix[si + 0] 168 | dst.Pix[di + 1] = src.Pix[si + 1] 169 | dst.Pix[di + 2] = src.Pix[si + 2] 170 | default: 171 | var tmp uint16 172 | tmp = uint16(src.Pix[si + 0]) * 0xff / uint16(a) 173 | dst.Pix[di + 0] = uint8(tmp) 174 | tmp = uint16(src.Pix[si + 1]) * 0xff / uint16(a) 175 | dst.Pix[di + 1] = uint8(tmp) 176 | tmp = uint16(src.Pix[si + 2]) * 0xff / uint16(a) 177 | dst.Pix[di + 2] = uint8(tmp) 178 | } 179 | 180 | di += 4 181 | si += 4 182 | 183 | } 184 | } 185 | }) 186 | case *image.RGBA64: 187 | parallel(dstH, func(partStart, partEnd int) { 188 | for dstY := partStart; dstY < partEnd; dstY++ { 189 | di := dst.PixOffset(0, dstY) 190 | si := src.PixOffset(srcMinX, srcMinY + dstY) 191 | for dstX := 0; dstX < dstW; dstX++ { 192 | 193 | a := src.Pix[si + 6] 194 | dst.Pix[di + 3] = a 195 | switch a { 196 | case 0: 197 | dst.Pix[di + 0] = 0 198 | dst.Pix[di + 1] = 0 199 | dst.Pix[di + 2] = 0 200 | case 0xff: 201 | dst.Pix[di + 0] = src.Pix[si + 0] 202 | dst.Pix[di + 1] = src.Pix[si + 2] 203 | dst.Pix[di + 2] = src.Pix[si + 4] 204 | default: 205 | var tmp uint16 206 | tmp = uint16(src.Pix[si + 0]) * 0xff / uint16(a) 207 | dst.Pix[di + 0] = uint8(tmp) 208 | tmp = uint16(src.Pix[si + 2]) * 0xff / uint16(a) 209 | dst.Pix[di + 1] = uint8(tmp) 210 | tmp = uint16(src.Pix[si + 4]) * 0xff / uint16(a) 211 | dst.Pix[di + 2] = uint8(tmp) 212 | } 213 | 214 | di += 4 215 | si += 8 216 | 217 | } 218 | } 219 | }) 220 | 221 | case *image.Gray: 222 | parallel(dstH, func(partStart, partEnd int) { 223 | for dstY := partStart; dstY < partEnd; dstY++ { 224 | di := dst.PixOffset(0, dstY) 225 | si := src.PixOffset(srcMinX, srcMinY + dstY) 226 | for dstX := 0; dstX < dstW; dstX++ { 227 | 228 | c := src.Pix[si] 229 | dst.Pix[di + 0] = c 230 | dst.Pix[di + 1] = c 231 | dst.Pix[di + 2] = c 232 | dst.Pix[di + 3] = 0xff 233 | 234 | di += 4 235 | si += 1 236 | 237 | } 238 | } 239 | }) 240 | 241 | case *image.Gray16: 242 | parallel(dstH, func(partStart, partEnd int) { 243 | for dstY := partStart; dstY < partEnd; dstY++ { 244 | di := dst.PixOffset(0, dstY) 245 | si := src.PixOffset(srcMinX, srcMinY + dstY) 246 | for dstX := 0; dstX < dstW; dstX++ { 247 | 248 | c := src.Pix[si] 249 | dst.Pix[di + 0] = c 250 | dst.Pix[di + 1] = c 251 | dst.Pix[di + 2] = c 252 | dst.Pix[di + 3] = 0xff 253 | 254 | di += 4 255 | si += 2 256 | 257 | } 258 | } 259 | }) 260 | 261 | case *image.YCbCr: 262 | parallel(dstH, func(partStart, partEnd int) { 263 | for dstY := partStart; dstY < partEnd; dstY++ { 264 | di := dst.PixOffset(0, dstY) 265 | for dstX := 0; dstX < dstW; dstX++ { 266 | 267 | srcX := srcMinX + dstX 268 | srcY := srcMinY + dstY 269 | siy := src.YOffset(srcX, srcY) 270 | sic := src.COffset(srcX, srcY) 271 | r, g, b := color.YCbCrToRGB(src.Y[siy], src.Cb[sic], src.Cr[sic]) 272 | dst.Pix[di + 0] = r 273 | dst.Pix[di + 1] = g 274 | dst.Pix[di + 2] = b 275 | dst.Pix[di + 3] = 0xff 276 | 277 | di += 4 278 | 279 | } 280 | } 281 | }) 282 | 283 | case *image.Paletted: 284 | plen := len(src.Palette) 285 | pnew := make([]color.NRGBA, plen) 286 | for i := 0; i < plen; i++ { 287 | pnew[i] = color.NRGBAModel.Convert(src.Palette[i]).(color.NRGBA) 288 | } 289 | 290 | parallel(dstH, func(partStart, partEnd int) { 291 | for dstY := partStart; dstY < partEnd; dstY++ { 292 | di := dst.PixOffset(0, dstY) 293 | si := src.PixOffset(srcMinX, srcMinY + dstY) 294 | for dstX := 0; dstX < dstW; dstX++ { 295 | 296 | c := pnew[src.Pix[si]] 297 | dst.Pix[di + 0] = c.R 298 | dst.Pix[di + 1] = c.G 299 | dst.Pix[di + 2] = c.B 300 | dst.Pix[di + 3] = c.A 301 | 302 | di += 4 303 | si += 1 304 | 305 | } 306 | } 307 | }) 308 | 309 | default: 310 | parallel(dstH, func(partStart, partEnd int) { 311 | for dstY := partStart; dstY < partEnd; dstY++ { 312 | di := dst.PixOffset(0, dstY) 313 | for dstX := 0; dstX < dstW; dstX++ { 314 | 315 | c := color.NRGBAModel.Convert(imgfile.At(srcMinX + dstX, srcMinY + dstY)).(color.NRGBA) 316 | dst.Pix[di + 0] = c.R 317 | dst.Pix[di + 1] = c.G 318 | dst.Pix[di + 2] = c.B 319 | dst.Pix[di + 3] = c.A 320 | 321 | di += 4 322 | 323 | } 324 | } 325 | }) 326 | 327 | } 328 | return dst.Pix 329 | } 330 | 331 | var parallelizationEnabled = true 332 | 333 | // if GOMAXPROCS = 1: no goroutines used 334 | // if GOMAXPROCS > 1: spawn N=GOMAXPROCS workers in separate goroutines 335 | func parallel(dataSize int, fn func(partStart, partEnd int)) { 336 | numGoroutines := 1 337 | partSize := dataSize 338 | 339 | if parallelizationEnabled { 340 | numProcs := runtime.GOMAXPROCS(0) 341 | if numProcs > 1 { 342 | numGoroutines = numProcs 343 | partSize = dataSize / (numGoroutines * 10) 344 | if partSize < 1 { 345 | partSize = 1 346 | } 347 | } 348 | } 349 | if numGoroutines == 1 { 350 | fn(0, dataSize) 351 | } else { 352 | var wg sync.WaitGroup 353 | wg.Add(numGoroutines) 354 | idx := uint64(0) 355 | 356 | for p := 0; p < numGoroutines; p++ { 357 | go func() { 358 | defer wg.Done() 359 | for { 360 | partStart := int(atomic.AddUint64(&idx, uint64(partSize))) - partSize 361 | if partStart >= dataSize { 362 | break 363 | } 364 | partEnd := partStart + partSize 365 | if partEnd > dataSize { 366 | partEnd = dataSize 367 | } 368 | fn(partStart, partEnd) 369 | } 370 | }() 371 | } 372 | wg.Wait() 373 | } 374 | } 375 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package imageio 2 | 3 | import ( 4 | "log" 5 | "testing" 6 | ) 7 | 8 | func TestGetFFmpegExe(t *testing.T) { 9 | exe, err := GetFFmpegExe() 10 | if err != nil { 11 | log.Fatal(err) 12 | } 13 | log.Printf(exe) 14 | } 15 | 16 | func TestGetPlatform(t *testing.T) { 17 | plat := GetPlatform() 18 | log.Printf(plat) 19 | } 20 | 21 | //func TestCheckIfFileExecutable(t *testing.T) { 22 | // plat := GetPlatform() 23 | // _, err := CheckIfFileExecutable(FnamePerPlatform[plat]) 24 | // if err != nil { 25 | // log.Fatal(err) 26 | // } 27 | //} 28 | // 29 | //func TestLoadImage(t *testing.T) { 30 | // _, err := LoadImage("images/image720x720.png") 31 | // if err != nil { 32 | // log.Fatal(err) 33 | // } 34 | //} 35 | --------------------------------------------------------------------------------