-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
244 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,7 @@ | ||
# photo-layouts | ||
照片排版程序 | ||
|
||
a simple photo layout program | ||
|
||
## License | ||
|
||
MIT |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} | ||
} |