github.com/richardwilkes/toolbox@v1.121.0/formats/icon/icon.go (about) 1 // Copyright (c) 2016-2024 by Richard A. Wilkes. All rights reserved. 2 // 3 // This Source Code Form is subject to the terms of the Mozilla Public 4 // License, version 2.0. If a copy of the MPL was not distributed with 5 // this file, You can obtain one at http://mozilla.org/MPL/2.0/. 6 // 7 // This Source Code Form is "Incompatible With Secondary Licenses", as 8 // defined by the Mozilla Public License, version 2.0. 9 10 package icon 11 12 import ( 13 "image" 14 15 "golang.org/x/image/draw" 16 ) 17 18 // Stack a set of images on top of each other, producing a new image. The first image in the series will be on the 19 // bottom and the last will be on the top. If the images are of different sizes, the resulting image will be the size of 20 // the largest image and all other images will be centered within that area. 21 func Stack(images ...image.Image) image.Image { 22 var width, height int 23 for _, img := range images { 24 bounds := img.Bounds() 25 if width < bounds.Dx() { 26 width = bounds.Dx() 27 } 28 if height < bounds.Dy() { 29 height = bounds.Dy() 30 } 31 } 32 base := image.NewRGBA(image.Rect(0, 0, width, height)) 33 for _, img := range images { 34 bounds := img.Bounds() 35 w := bounds.Dx() 36 h := bounds.Dy() 37 draw.Copy(base, image.Pt((width-w)/2, (height-h)/2), img, bounds, draw.Over, nil) 38 } 39 return base 40 } 41 42 // ImageAt provides storage for an image and an origin point. 43 type ImageAt struct { 44 Image image.Image 45 Origin image.Point 46 } 47 48 // StackAt stacks a set of images on top of each other, producing a new image. The first image in the series will be on 49 // the bottom and the last will be on the top. The resulting image will be the size of the largest area covered based on 50 // each image's size plus origin. Note that if an origin has a negative value, it will be normalized such that the 51 // largest negative will become the new origin for the resulting image. 52 func StackAt(images ...*ImageAt) image.Image { 53 var x, y, width, height int 54 for _, img := range images { 55 bounds := img.Image.Bounds() 56 if x > img.Origin.X { 57 x = img.Origin.X 58 } 59 if y > img.Origin.Y { 60 y = img.Origin.Y 61 } 62 w := bounds.Dx() + img.Origin.X 63 if width < w { 64 width = w 65 } 66 h := bounds.Dy() + img.Origin.Y 67 if height < h { 68 height = h 69 } 70 } 71 base := image.NewRGBA(image.Rect(0, 0, width, height)) 72 for _, img := range images { 73 bounds := img.Image.Bounds() 74 draw.Copy(base, image.Pt(img.Origin.X-x, img.Origin.Y-y), img.Image, bounds, draw.Over, nil) 75 } 76 return base 77 } 78 79 // Scale an image. 80 func Scale(img image.Image, width, height int) image.Image { 81 bounds := img.Bounds() 82 w := bounds.Dx() 83 h := bounds.Dy() 84 if w == width && h == height { 85 return img 86 } 87 scaled := image.NewRGBA(image.Rect(0, 0, width, height)) 88 draw.CatmullRom.Scale(scaled, scaled.Bounds(), img, bounds, draw.Over, nil) 89 return scaled 90 } 91 92 // ScaleTo scales the image to the desired sizes. If an image cannot be scaled exactly to the desired size, it will be 93 // scaled proportionally and then centered within the available space. 94 func ScaleTo(img image.Image, sizes []image.Point) []image.Image { 95 list := make([]image.Image, 0, len(sizes)) 96 bounds := img.Bounds() 97 width := bounds.Dx() 98 height := bounds.Dy() 99 for _, size := range sizes { 100 w, h := ScaleProportionally(width, height, size.X, size.Y) 101 scaled := Scale(img, w, h) 102 if w != size.X || h != size.Y { 103 scaled = StackAt( 104 &ImageAt{ 105 Image: image.NewRGBA(image.Rect(0, 0, size.X, size.Y)), 106 Origin: image.Point{}, 107 }, 108 &ImageAt{ 109 Image: scaled, 110 Origin: image.Pt((size.X-w)/2, (size.Y-h)/2), 111 }, 112 ) 113 } 114 list = append(list, scaled) 115 } 116 return list 117 } 118 119 // ScaleProportionally returns the width and height that are closest to the desired values without distorting the size. 120 func ScaleProportionally(currentWidth, currentHeight, desiredWidth, desiredHeight int) (width, height int) { 121 if desiredWidth != currentWidth || desiredHeight != currentHeight { 122 scaleX := float64(desiredWidth) / float64(currentWidth) 123 scaleY := float64(desiredHeight) / float64(currentHeight) 124 scale := min(scaleX, scaleY) 125 return int(float64(currentWidth) * scale), int(float64(currentHeight) * scale) 126 } 127 return currentWidth, currentHeight 128 } 129 130 // ScaleUpProportionally returns the width and height that are closest to the desired values without distorting the 131 // size, but won't decrease the current values. 132 func ScaleUpProportionally(currentWidth, currentHeight, desiredWidth, desiredHeight int) (width, height int) { 133 if desiredWidth != currentWidth || desiredHeight != currentHeight { 134 scaleX := float64(desiredWidth) / float64(currentWidth) 135 scaleY := float64(desiredHeight) / float64(currentHeight) 136 if scale := min(scaleX, scaleY); scale > 1 { 137 return int(float64(currentWidth) * scale), int(float64(currentHeight) * scale) 138 } 139 } 140 return currentWidth, currentHeight 141 } 142 143 // ScaleDownProportionally returns the width and height that are closest to the desired values without distorting the 144 // size, but won't increase the current values. 145 func ScaleDownProportionally(currentWidth, currentHeight, desiredWidth, desiredHeight int) (width, height int) { 146 if desiredWidth != currentWidth || desiredHeight != currentHeight { 147 scaleX := float64(desiredWidth) / float64(currentWidth) 148 scaleY := float64(desiredHeight) / float64(currentHeight) 149 if scale := min(scaleX, scaleY); scale < 1 { 150 return int(float64(currentWidth) * scale), int(float64(currentHeight) * scale) 151 } 152 } 153 return currentWidth, currentHeight 154 }