Skip to content

Instantly share code, notes, and snippets.

@apsun
Created July 26, 2020 04:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save apsun/8f1493e56547df191a5d6c4ccd9610de to your computer and use it in GitHub Desktop.
Save apsun/8f1493e56547df191a5d6c4ccd9610de to your computer and use it in GitHub Desktop.
// Converts images into PNG format with the top-left corner transparent.
// Signal treats images sent via the keyboard with a transparent top-left
// corner as actual stickers, skipping one button click. This script
// automates batch conversion into this "sticker" format, for use with
// uSticker. Currently doesn't work with animated GIFs.
package main
import (
"flag"
"fmt"
"image"
_ "image/gif"
_ "image/jpeg"
"image/png"
"io"
"os"
"path/filepath"
"strings"
)
var srcFlag = flag.String("src", "", "directory to read original images from")
var destFlag = flag.String("dest", "", "directory to write converted images to")
func convertPNG(r io.Reader, w io.Writer) error {
// Read original image
src, _, err := image.Decode(r)
if err != nil {
return err
}
// Convert image to RGBA color space
var dest *image.RGBA
srcRGBA, isRGBA := src.(*image.RGBA)
if isRGBA {
dest = srcRGBA
} else {
bounds := src.Bounds()
dest = image.NewRGBA(bounds)
for y := 0; y < bounds.Max.Y; y++ {
for x := 0; x < bounds.Max.X; x++ {
c := src.At(x, y)
dest.Set(x, y, c)
}
}
}
// Set (0,0) to transparent color. Since we're using premultiplied
// RGBA, we also have to scale the other color components.
topLeft := dest.RGBAAt(0, 0)
if topLeft.A == 255 {
topLeft.R = uint8(uint16(topLeft.R) * 254 / 255)
topLeft.G = uint8(uint16(topLeft.G) * 254 / 255)
topLeft.B = uint8(uint16(topLeft.B) * 254 / 255)
topLeft.A = 254
dest.SetRGBA(0, 0, topLeft)
}
// Write out converted image
return png.Encode(w, dest)
}
func convertAll(srcDir string, destDir string) error {
return filepath.Walk(srcDir, func(path string, info os.FileInfo, err error) error {
// Ignore directories
if info.IsDir() {
return nil
}
// Open source image for reading
r, err := os.Open(path)
if err != nil {
return fmt.Errorf("failed to open source image for reading: %v", err)
}
defer r.Close()
// Create output image file
relPath, err := filepath.Rel(srcDir, path)
if err != nil {
return fmt.Errorf("couldn't compute image path: %v", err)
}
relPath = strings.TrimSuffix(relPath, filepath.Ext(relPath)) + ".png"
outPath := filepath.Join(destDir, relPath)
err = os.MkdirAll(filepath.Dir(outPath), os.ModePerm)
if err != nil {
return fmt.Errorf("failed to create output parent dir: %v", err)
}
w, err := os.Create(outPath)
if err != nil {
return fmt.Errorf("failed to create output file: %v", err)
}
// Convert to output format
err = convertPNG(r, w)
if err != nil {
return fmt.Errorf("failed to convert image: %v", err)
}
// Ensure errors during flush are caught
err = w.Close()
if err != nil {
return fmt.Errorf("failed to flush output file: %v", err)
}
return nil
})
}
func usage() {
fmt.Fprintf(os.Stderr, "usage: img2stkr -src <from> -dest <to>\n")
os.Exit(1)
}
func main() {
flag.Parse()
if *srcFlag == "" || *destFlag == "" {
usage()
}
err := convertAll(*srcFlag, *destFlag)
if err != nil {
fmt.Printf(err.Error())
os.Exit(1)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment