Skip to content

Commit

Permalink
removed viper deps from pkg, introduced options struct for previously…
Browse files Browse the repository at this point in the history
… used viper opts, updated readme (#20)

* removed viper deps from pkg, introduced options struct for previously used viper opts, updated readme

* removed unused struct from test file
  • Loading branch information
James-Pickett authored Jan 17, 2022
1 parent e25df39 commit 0fe2579
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 47 deletions.
20 changes: 16 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,32 +52,44 @@ While my personal preference would by yml, I think toml is easer for non coder t
```toml
[[images]]
# this image will be output to the image_output_directory as my_new_image.png
name = "my_new_image"
name = "logs_to_boards"

# image parts are read from top to bottom bulding the image from left to right
[[images.image_parts]]

# icombo will serach for log.png in the configured image_input_directory
file_name = "first_part"
file_name = "log"

# icombo will repeat the image based on count, if count is 1 you can remove this line
count = 2

# arrow faces up by default and rotates counter clockwise
[[images.image_parts]]

file_name = "arrow"

# icombo will rotate the image based on rotation_degrees counter clockwise
rotation_degrees = 180
rotation_degrees = 270

# simplest way to define a part, will add it once not changing rotation
[[images.image_parts]]
file_name = "second_part"
file_name = "board"
```
* save your file and run icombo then check your image_output_directory
* you can also define the following configurations in the icombo.toml file
```toml
[options]
# where icombo will look for the parts to build images
image_input_directory = "./image_parts"

# where icombo will output images
image_output_directory = "./output_images"

# the pixel size of a side of an image part
# this configuration will make each image part a square whose sides are 64 pixels
# if my image had 4 parts, the height would be 64 pixels and the width 256 pixels (4 * 64)
image_part_size_pixels = 64

# number of images that can be built simultaneously, if left at zero icombo will attempt to build all images simultaneously
concurrency = 0
```
Expand Down
20 changes: 20 additions & 0 deletions example/icombo.toml
Original file line number Diff line number Diff line change
@@ -1,20 +1,40 @@
[options]
# where icombo will look for the parts to build images
image_input_directory = "./image_parts"

# where icombo will output images
image_output_directory = "./output_images"

# the pixel size of a side of an image part
# this configuration will make each image part a square whose sides are 64 pixels
# if my image had 4 parts, the height would be 64 pixels and the width 256 pixels (4 * 64)
image_part_size_pixels = 64

# number of images that can be built simultaneously, if left at zero icombo will attempt to build all images simultaneously
concurrency = 0

[[images]]
# this image will be output to the image_output_directory as my_new_image.png
name = "logs_to_boards"

# image parts are read from top to bottom bulding the image from left to right
[[images.image_parts]]

# icombo will serach for log.png in the configured image_input_directory
file_name = "log"

# icombo will repeat the image based on count, if count is 1 you can remove this line
count = 2

# arrow faces up by default and rotates counter clockwise
[[images.image_parts]]

file_name = "arrow"

# icombo will rotate the image based on rotation_degrees counter clockwise
rotation_degrees = 270

# simplest way to define a part, will add it once not changing rotation
[[images.image_parts]]
file_name = "board"

Expand Down
63 changes: 30 additions & 33 deletions pkg/icombo/imgproc.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,24 +9,25 @@ import (
"strings"

"github.com/disintegration/imaging"
"github.com/spf13/viper"
)

type ProcessImagesInput struct {
ImageDefs []ImageDef `mapstructure:"images"`
ImageDefs []ImageDef `mapstructure:"images"`
Options ProcessImagesOptions `mapstructure:"options"`
}

type ProcessImagesOptions struct {
ImageOutputDirectory string `mapstructure:"image_output_directory"`
ImageInputDirectory string `mapstructure:"image_input_directory"`
Concurrency int `mapstructure:"concurrency"`
ImagePartSizePixels int `mapstructure:"image_part_size_pixels"`
}

type ImageDef struct {
Name string `mapstructure:"name"`
ImagePartDefs []ImagePartDef `mapstructure:"image_parts"`
}

func (i ImageDef) fileOutputPath() string {
outputDir := viper.Get("image_output_directory")
path := fmt.Sprint(outputDir, "/", i.Name)
return resolvePngExtention(path)
}

func (img ImageDef) imagePartCount() int {
count := 0
for i := 0; i < len(img.ImagePartDefs); i++ {
Expand All @@ -41,12 +42,6 @@ type ImagePartDef struct {
Count int `mapstructure:"count"`
}

func (i ImagePartDef) filePath() string {
inputDir := viper.Get("image_input_directory")
path := fmt.Sprint(inputDir, "/", i.FileName)
return resolvePngExtention(path)
}

func (i ImagePartDef) countAtLeast1() int {
count := i.Count
if count > 0 {
Expand All @@ -61,13 +56,18 @@ func ProcessImages(input ProcessImagesInput) error {
imageDefsChan := make(chan ImageDef, imageCount)
errorChan := make(chan error, imageCount)

concurrency := viper.GetInt("concurrency")
if concurrency <= 0 || concurrency > imageCount {
concurrency := input.Options.Concurrency
if input.Options.Concurrency <= 0 || input.Options.Concurrency > imageCount {
concurrency = imageCount
}

// create output dir if not exists
if _, err := os.Stat(input.Options.ImageOutputDirectory); os.IsNotExist(err) {
os.Mkdir(input.Options.ImageOutputDirectory, os.ModeAppend)
}

for i := 0; i < concurrency; i++ {
go processImageWorker(imageDefsChan, errorChan)
go processImageWorker(imageDefsChan, errorChan, input.Options)
}

for i := 0; i < imageCount; i++ {
Expand All @@ -86,47 +86,48 @@ func ProcessImages(input ProcessImagesInput) error {
return lastErr
}

func processImageWorker(imagedDefs <-chan ImageDef, errors chan<- error) {
func processImageWorker(imagedDefs <-chan ImageDef, errors chan<- error, opts ProcessImagesOptions) {
for imageDef := range imagedDefs {
errors <- createImage(imageDef)
errors <- createImage(imageDef, opts)
}
}

func createImage(imageDef ImageDef) error {
func createImage(imageDef ImageDef, opts ProcessImagesOptions) error {

finalImage := imaging.New(imagePartSize()*imageDef.imagePartCount(), imagePartSize(), color.Black)
finalImage := imaging.New(opts.ImagePartSizePixels*imageDef.imagePartCount(), opts.ImagePartSizePixels, color.Black)
totalImagePartCount := 0

for i := 0; i < len(imageDef.ImagePartDefs); i++ {

imagePartDef := imageDef.ImagePartDefs[i]

for j := 0; j < imagePartDef.countAtLeast1(); j++ {
rawImagePartSrc, err := imaging.Open(imagePartDef.filePath())

imagePartPath := fmt.Sprint(opts.ImageInputDirectory, "/", resolvePngExtention(imagePartDef.FileName))

rawImagePartSrc, err := imaging.Open(imagePartPath)
if err != nil {
return err
}

imagePartSrc := imaging.Resize(rawImagePartSrc, imagePartSize(), imagePartSize(), imaging.Lanczos)
imagePartSrc := imaging.Resize(rawImagePartSrc, opts.ImagePartSizePixels, opts.ImagePartSizePixels, imaging.Lanczos)
imagePartSrc = imaging.Rotate(imagePartSrc, imagePartDef.Rotation, color.Black)
finalImage = imaging.Paste(finalImage, imagePartSrc, image.Point{imagePartSize() * totalImagePartCount, 0})
finalImage = imaging.Paste(finalImage, imagePartSrc, image.Point{opts.ImagePartSizePixels * totalImagePartCount, 0})

totalImagePartCount++
}
}

if err := saveImage(finalImage, imageDef.fileOutputPath()); err != nil {
fileOutputPath := fmt.Sprint(opts.ImageOutputDirectory, "/", resolvePngExtention(imageDef.Name))

if err := saveImage(finalImage, fileOutputPath); err != nil {
return err
}

return nil
}

func saveImage(img *image.NRGBA, path string) error {
outputDir := viper.GetString("image_output_directory")
if _, err := os.Stat(outputDir); os.IsNotExist(err) {
os.Mkdir(outputDir, os.ModeAppend)
}
if err := imaging.Save(img, path); err != nil {
return err
}
Expand All @@ -139,7 +140,3 @@ func resolvePngExtention(path string) string {
}
return fmt.Sprint(path, ".png")
}

func imagePartSize() int {
return viper.GetInt("image_part_size_pixels")
}
21 changes: 11 additions & 10 deletions pkg/icombo/imgproc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,19 @@ import (
"log"
"os"
"testing"

"github.com/spf13/viper"
)

const TEST_IMAGE_OUTPUT_DIR = "./test_image_output"
const TEST_IMAGE_INPUT_DIR = "../../example/image_parts"
const TEST_IMAGE_PART_PIXEL_SIZE = 32

var testConfig = ProcessImagesInput{
Options: ProcessImagesOptions{
Concurrency: 0,
ImageOutputDirectory: TEST_IMAGE_OUTPUT_DIR,
ImageInputDirectory: TEST_IMAGE_INPUT_DIR,
ImagePartSizePixels: TEST_IMAGE_PART_PIXEL_SIZE,
},
ImageDefs: []ImageDef{
{
Name: "boards_to_logs",
Expand Down Expand Up @@ -48,17 +56,10 @@ var testConfig = ProcessImagesInput{

func setupProcessImagesSuccess(tb testing.TB) func(tb testing.TB) {
log.Println("setup suite")

testOutputDirectory := "./test_image_output"

viper.Set("image_output_directory", testOutputDirectory)
viper.Set("image_part_size_pixels", 32)
viper.Set("image_input_directory", "../../example/image_parts")

// Return a function to teardown the test
return func(tb testing.TB) {
log.Println("teardown suite")
os.RemoveAll(testOutputDirectory)
os.RemoveAll(TEST_IMAGE_OUTPUT_DIR)
}
}

Expand Down

0 comments on commit 0fe2579

Please sign in to comment.