Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
ne8ztwk2 committed Jul 18, 2024
1 parent 70b3001 commit 280fa24
Show file tree
Hide file tree
Showing 14 changed files with 244 additions and 1 deletion.
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,7 @@
# photo-layouts
照片排版程序

a simple photo layout program

## License

MIT
41 changes: 41 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package main

import (
"fmt"
"os"

photoLayouts "github.com/leptobo/photo-layouts"
"github.com/spf13/cobra"
)

var photo photoLayouts.Photo

var rootCmd = &cobra.Command{
Use: "pl",
Short: "PL is a simple photo layout program",
Long: `Complete documentation is available at https://github.com/leptobo/photo-layouts`,
Run: func(cmd *cobra.Command, args []string) {
file, err := photoLayouts.Layout(&photo)
if err != nil {
cmd.PrintErrf("error: %v\n", err)
os.Exit(1)
}

cmd.Printf("success, save to: %s\n", file)
},
}

func main() {
rootCmd.PersistentFlags().StringVarP(&photo.File, "file", "f", "", "photo file")
rootCmd.PersistentFlags().Float64VarP(&photo.ContainerWidth, "ppwidth", "W", 0, "photo paper width (mm)")
rootCmd.PersistentFlags().Float64VarP(&photo.ContainerHeight, "ppheight", "H", 0, "photo paper height (mm)")
rootCmd.PersistentFlags().Float64Var(&photo.PhotoWidth, "pw", 0, "photo width (mm)")
rootCmd.PersistentFlags().Float64Var(&photo.PhotoHeight, "ph", 0, "photo height (mm)")
rootCmd.PersistentFlags().StringVarP(&photo.Color, "color", "c", "", "set background color (hex)")
rootCmd.PersistentFlags().Float64VarP(&photo.Dpi, "dpi", "d", 0, "dpi")

if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
Binary file added example/James.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/James_layout.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/John.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/John_layout.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/Michael.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/Michael_layout.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/William.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/William_layout.jpeg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
module github.com/leptobo/photo-layouts

go 1.22.5

require github.com/disintegration/imaging v1.6.2

require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 // indirect
)
15 changes: 15 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
148 changes: 148 additions & 0 deletions photo-layouts.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package photoLayouts

import (
"errors"
"fmt"
"image"
"image/color"
"image/draw"
"image/jpeg"
"math"
"os"
"strings"

"github.com/disintegration/imaging"
)

type Photo struct {
File string
LayoutFile string
ContainerWidth float64
ContainerHeight float64
PhotoWidth float64
PhotoHeight float64
Color string
Dpi float64
}

func Layout(p *Photo) (string, error) {
// convert millimeters (mm) to pixels (px)
p.ContainerHeight = ConverMmToPx(p.Dpi, p.ContainerHeight)
p.ContainerWidth = ConverMmToPx(p.Dpi, p.ContainerWidth)
p.PhotoWidth = ConverMmToPx(p.Dpi, p.PhotoWidth)
p.PhotoHeight = ConverMmToPx(p.Dpi, p.PhotoHeight)

// If the photo size exceeds the container size, set the photo to the container size.
if p.PhotoWidth >= p.ContainerWidth && p.PhotoHeight >= p.ContainerHeight {
p.PhotoWidth, p.PhotoHeight = p.ContainerWidth, p.ContainerHeight
}

// Determine if the container needs to be rotated.
isRotate := (p.ContainerWidth/p.PhotoHeight)*(p.ContainerHeight/p.PhotoWidth) > (p.ContainerWidth/p.PhotoWidth)*(p.ContainerHeight/p.PhotoHeight)
// If rotation is needed, swap the container's width and height.
if isRotate {
p.ContainerHeight, p.ContainerWidth = p.ContainerWidth, p.ContainerHeight
}
// Calculate how many photos can fit in each direction of the container.
floorX := math.Floor(p.ContainerWidth / p.PhotoWidth)
floorY := math.Floor(p.ContainerHeight / p.PhotoHeight)

// make container
containerWidth := int(p.ContainerWidth)
containerHeight := int(p.ContainerHeight)
containerRectangle := image.Rect(0, 0, containerWidth, containerHeight)
containerImg := image.NewNRGBA(containerRectangle)
rgba, err := ParseHexColor(p.Color)
if err != nil {
return "", err
}
// Set the background color.
for x := 0; x < containerWidth; x++ {
for y := 0; y < containerHeight; y++ {
containerImg.Set(x, y, rgba)
}
}

file, err := os.Open(p.File)
if err != nil {
return "", err
}
defer file.Close()
rawImg, err := jpeg.Decode(file)
if err != nil {
return "", err
}

// resize the photos
rawImg = imaging.Resize(rawImg, int(p.PhotoWidth), int(p.PhotoHeight), imaging.Lanczos)

// Calculate the spacing: (containerWidth−(imageWidth×imageCount))/(imageCount+1)(containerWidth−(imageWidth×imageCount))/(imageCount+1)
gapX := int((p.ContainerWidth - (float64(rawImg.Bounds().Dx()) * floorX)) / (floorX + 1))
gapY := int((p.ContainerHeight - (float64(rawImg.Bounds().Dy()) * floorY)) / (floorY + 1))

// layout
for i := 0; i < int(floorX); i++ {
for j := 0; j < int(floorY); j++ {
offsetX := i*(rawImg.Bounds().Dx()+gapX) + gapX
offsetY := j*(rawImg.Bounds().Dy()+gapY) + gapY

draw.Draw(containerImg, containerRectangle.Add(image.Pt(offsetX, offsetY)), rawImg, rawImg.Bounds().Min, draw.Src)
}
}

// save to xx_layout.jpeg
layoutFilePath := fmt.Sprintf("%s%s", strings.Split(p.File, ".")[0], "_layout.jpeg")
saveFile, err := os.Create(layoutFilePath)
if err != nil {
return "", err
}
defer file.Close()
if err := jpeg.Encode(saveFile, containerImg, nil); err != nil {
return "", err
}

return layoutFilePath, nil
}

const INCH float64 = 25.4

func ConverMmToPx(dpi, mm float64) (px float64) {
return (dpi * mm) / INCH
}

var errInvalidFormat = errors.New("invalid format")

func ParseHexColor(hexColor string) (c color.RGBA, err error) {
c.A = 0xff

if hexColor[0] != '#' {
return c, errInvalidFormat
}

hexToByte := func(b byte) byte {
switch {
case b >= '0' && b <= '9':
return b - '0'
case b >= 'a' && b <= 'f':
return b - 'a' + 10
case b >= 'A' && b <= 'F':
return b - 'A' + 10
}
err = errInvalidFormat
return 0
}

switch len(hexColor) {
case 7:
c.R = hexToByte(hexColor[1])<<4 + hexToByte(hexColor[2])
c.G = hexToByte(hexColor[3])<<4 + hexToByte(hexColor[4])
c.B = hexToByte(hexColor[5])<<4 + hexToByte(hexColor[6])
case 4:
c.R = hexToByte(hexColor[1]) * 17
c.G = hexToByte(hexColor[2]) * 17
c.B = hexToByte(hexColor[3]) * 17
default:
err = errInvalidFormat
}
return
}
22 changes: 22 additions & 0 deletions photo-layouts_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package photoLayouts_test

import (
"testing"

photoLayouts "github.com/leptobo/photo-layouts"
)

func TestLayout(t *testing.T) {
photos := []photoLayouts.Photo{
{File: "example/James.jpg", Dpi: 300, PhotoWidth: 25, PhotoHeight: 35, ContainerWidth: 127, ContainerHeight: 89, Color: "#000000"}, // 1 5
{File: "example/Michael.jpg", Dpi: 300, PhotoWidth: 38, PhotoHeight: 51, ContainerWidth: 127, ContainerHeight: 89, Color: "#FFFFFF"}, // 2 5
{File: "example/William.jpg", Dpi: 300, PhotoWidth: 25, PhotoHeight: 35, ContainerWidth: 152, ContainerHeight: 102, Color: "#808080"}, // 1 6
{File: "example/John.jpg", Dpi: 300, PhotoWidth: 38, PhotoHeight: 51, ContainerWidth: 152, ContainerHeight: 102, Color: "#808080"}, // 2 6
// add more...
}
for _, photo := range photos {
if _, err := photoLayouts.Layout(&photo); err != nil {
t.Error(err)
}
}
}

0 comments on commit 280fa24

Please sign in to comment.