Skip to content

Commit

Permalink
Merge pull request #3 from sincerefly/feature/style-pineapple
Browse files Browse the repository at this point in the history
feat: support pineapple style
  • Loading branch information
sincerefly authored Jul 30, 2024
2 parents 004c949 + 43583bc commit 53710d1
Show file tree
Hide file tree
Showing 13 changed files with 359 additions and 0 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,19 @@ $ capybara style melon
</tr>
</table>

```bash
$ capybara style pineapple
```

<table>
<tr>
<td>style: pineapple</td>
</tr>
<tr>
<td><img src="docs/image/style-pineapple.webp" width=270></td>
</tr>
</table>

### Help

Different styles support different parameters. Please refer to the documentation for details
Expand Down
1 change: 1 addition & 0 deletions cmd/cmds_style.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ func init() {
styleCmd.AddCommand(style.SimpleCmd)
styleCmd.AddCommand(style.TextBottomCmd)
styleCmd.AddCommand(style.LogoMelonCmd)
styleCmd.AddCommand(style.PineappleCmd)
}

var styleCmd = &cobra.Command{
Expand Down
44 changes: 44 additions & 0 deletions cmd/style/pineapple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package style

import (
"github.com/sincerefly/capybara/base/log"
"github.com/sincerefly/capybara/cmd/cmdutils"
"github.com/sincerefly/capybara/service/style"
"github.com/sincerefly/capybara/utils/colorizer"
"github.com/spf13/cobra"
)

var PineappleCmd = &cobra.Command{
Use: "pineapple",
Short: "Style: retro style film time",
Run: func(cmd *cobra.Command, args []string) {

parameter := &style.PineappleParameter{}

input := cmdutils.GetParam(cmd.Flags(), "input")
parameter.SetInput(input)

output := cmdutils.GetParam(cmd.Flags(), "output")
parameter.SetOutput(output)

// color param
colorStr := cmdutils.GetParam(cmd.Flags(), "color")
col, err := colorizer.ToColor(colorStr)
if err != nil {
log.Fatal(err)
}
parameter.SetFontColor(col)

// run
log.Debugf("parameter: %s", parameter.JSONString())
style.NewStyleProcessor(style.StylePineapple, parameter).Run()
},
}

func init() {

flags := PineappleCmd.Flags()
flags.StringP("input", "i", "input", "specify input folder")
flags.StringP("output", "o", "output", "specify output folder")
flags.StringP("color", "c", "rgba(255, 140, 0, 230)", "specify font color")
}
Binary file added docs/image/style-pineapple.webp
Binary file not shown.
2 changes: 2 additions & 0 deletions resources/embed.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ const (
RobotLightTTF = "font/Robot/Robot-Light.ttf"
RobotMediumTTF = "font/Robot/Robot-Medium.ttf"
RobotRegularTTF = "font/Robot/Robot-Regular.ttf"
Digital7MonoTTF = "font/Digital-7/digital-7-mono.ttf"
Digital7TTF = "font/Digital-7/digital-7.ttf"
)

//go:embed logo/*
Expand Down
Binary file added resources/font/Digital-7/digital-7-mono.ttf
Binary file not shown.
Binary file added resources/font/Digital-7/digital-7.ttf
Binary file not shown.
5 changes: 5 additions & 0 deletions service/style/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
StyleSimple Style = "simple"
StyleTextBottom Style = "text_bottom"
StyleLogoMelon Style = "logo_melon"
StylePineapple Style = "pineapple"
)

type Parameterizable interface {
Expand Down Expand Up @@ -61,7 +62,11 @@ func (s *StyleProcessor) Run() {
case StyleLogoMelon:
params := s.params.(*LogoMelonParameter)
err = NewLogoMelonProcessor(params, fiStore).Run()
case StylePineapple:
params := s.params.(*PineappleParameter)
err = NewPineappleProcessor(params, fiStore).Run()
}

if err != nil {
log.Fatal(err)
}
Expand Down
204 changes: 204 additions & 0 deletions service/style/pineapple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
package style

import (
"fmt"
"github.com/disintegration/imaging"
"github.com/fogleman/gg"
"github.com/sincerefly/capybara/base/log"
"github.com/sincerefly/capybara/global"
"github.com/sincerefly/capybara/resources"
"github.com/sincerefly/capybara/service/style/styles_common"
"github.com/sincerefly/capybara/structure/fileitem"
"github.com/sincerefly/capybara/structure/layout"
"github.com/sincerefly/capybara/structure/size"
"github.com/sincerefly/capybara/structure/text"
"github.com/sincerefly/capybara/utils/exif"
"github.com/sincerefly/capybara/utils/ggwrapper"
"image"
"image/color"
"time"
)

type PineappleProcessor struct {
params *PineappleParameter
fiStore *fileitem.Store
}

func NewPineappleProcessor(params *PineappleParameter, fiStore *fileitem.Store) *PineappleProcessor {
return &PineappleProcessor{
params: params,
fiStore: fiStore,
}
}

func (s *PineappleProcessor) Run() error {
if s.fiStore == nil {
return nil
}

// parser exif meta data
newStore, err := styles_common.SupplementaryMetaToStore(s.fiStore)
if err != nil {
return err
}

if global.ParamNoParallelism {
fileitem.LoopExecutor(newStore, s.runner)
} else {
fileitem.PoolExecutor(newStore, s.runner)
}
return nil
}

func (s *PineappleProcessor) runner(fi fileitem.FileItem) error {

srcImageKey := fi.GetSourceKey()
outImageKey := fi.GetTargetKey()
meta := fi.GetExifMeta()

fontColor := s.params.FontColor()

img, err := imaging.Open(srcImageKey, imaging.AutoOrientation(true))
if err != nil {
log.Fatalf("failed to open image %v", err)
}

imgSize := size.Size{
Width: img.Bounds().Dx(),
Height: img.Bounds().Dy(),
}

dst := imaging.New(imgSize.Width, imgSize.Height, color.White)

// paste the original image onto a new background
dst = imaging.Paste(dst, img, image.Pt(0, 0))

// create draw context
dc := gg.NewContextForImage(dst)

// portrait image
if imgSize.Height > imgSize.Width {

dc.Translate(float64(imgSize.Width/2), float64(imgSize.Height/2))
dc.Rotate(90 * gg.Radians(1))
dc.Translate(-float64(imgSize.Height/2), -float64(imgSize.Width/2))

err := s.drawPortrait(dc, imgSize, meta, fontColor)
if err != nil {
log.Fatalf("failed to draw date stamp %v", err)
return err
}

dc.Translate(float64(imgSize.Height/2), float64(imgSize.Width/2))
dc.Rotate(-90 * gg.Radians(1))
dc.Translate(-float64(imgSize.Width/2), -float64(imgSize.Height/2))

} else {
err = s.drawHorizontal(dc, imgSize, meta, fontColor)
if err != nil {
log.Fatalf("failed to draw date stamp %v", err)
return err
}
}

err = imaging.Save(dc.Image(), outImageKey)
if err != nil {
log.Fatalf("failed to save image %v", err)
return err
}
log.Infof("with pineapple style saved to %s", outImageKey)
return nil
}

func (s *PineappleProcessor) fixedFontSize(size size.Size) float64 {
return float64(size.Height) / 25
}

func (s *PineappleProcessor) drawHorizontal(dc *gg.Context, imgSize size.Size, meta exif.Meta, fontColor color.Color) error {

// font size
fontSize := s.fixedFontSize(imgSize)

createDate, err := s.metaDateToDateStampFormat(meta.CreateDateSafe())
if err != nil {
log.Fatalf("convert datetime failed, %v", err)
}

dateTimeRt := text.NewRichText(
createDate,
resources.Digital7MonoTTF,
fontSize,
fontColor,
)
rtDc, _ := dateTimeRt.Context(imgSize.Width, imgSize.Height)
fontWidth, _ := rtDc.MeasureString(dateTimeRt.Text())

// set position
x := float64(imgSize.Width) - fontWidth - fontSize
y := float64(imgSize.Height) - fontSize + 50
drawPosition := layout.NewPosition(x, y)
dateTimeRt.SetPosition(drawPosition)

rTexts := []text.RichText{dateTimeRt}

if err := ggwrapper.DrawString(dc, rTexts); err != nil {
return err
}
return nil
}

func (s *PineappleProcessor) drawPortrait(dc *gg.Context, imgSize size.Size, meta exif.Meta, fontColor color.Color) error {

// font size
fontSize := s.fixedFontSize(imgSize)

createDate, err := s.metaDateToDateStampFormat(meta.CreateDateSafe())
if err != nil {
log.Fatalf("convert datetime failed, %v", err)
}

dateTimeRt := text.NewRichText(
createDate,
resources.Digital7MonoTTF,
fontSize,
fontColor,
)
rtDc, _ := dateTimeRt.Context(imgSize.Width, imgSize.Height)
fontWidth, fontHeight := rtDc.MeasureString(dateTimeRt.Text())

rotatedWidth := imgSize.Height
rotatedHeight := imgSize.Width

dateTimeRt.SetPosition(layout.NewPosition(float64(rotatedWidth)-fontWidth-200, float64(rotatedHeight)-fontHeight))
dateTimeRt.SetAnchor(layout.NewAnchor(0, 0))

rTexts := []text.RichText{dateTimeRt}

if err := ggwrapper.DrawStringAnchored(dc, rTexts); err != nil {
return err
}
return nil
}

// Output Format: '24 07 29 21:22
func (s *PineappleProcessor) metaDateToDateStampFormat(input string) (string, error) {
// Define the layout of the input time string
const inputLayout = "2006:01:02 15:04:05"

// Parse the input string to time.Time format
t, err := time.Parse(inputLayout, input)
if err != nil {
return "", err
}

// Define the desired output format
year := fmt.Sprintf("'%02d", t.Year()%100)
month := fmt.Sprintf("%02d", t.Month())
day := fmt.Sprintf("%02d", t.Day())
hours := fmt.Sprintf("%02d", t.Hour())
minutes := fmt.Sprintf("%02d", t.Minute())

// Concatenate to form the output string
output := fmt.Sprintf("%s %s %s %s:%s", year, month, day, hours, minutes)
return output, nil
}
51 changes: 51 additions & 0 deletions service/style/pineapple_parameter.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package style

import (
"encoding/json"
"image/color"
)

type PineappleParameter struct {
input string
output string
fontColor color.Color
}

func (p *PineappleParameter) JSONString() string {

resp := map[string]any{
"input": p.Input(),
"output": p.Output(),
"fontColor": p.FontColor(),
}

b, err := json.Marshal(resp)
if err != nil {
return ""
}
return string(b)
}

func (p *PineappleParameter) Input() string {
return p.input
}

func (p *PineappleParameter) SetInput(input string) {
p.input = input
}

func (p *PineappleParameter) Output() string {
return p.output
}

func (p *PineappleParameter) SetOutput(output string) {
p.output = output
}

func (p *PineappleParameter) FontColor() color.Color {
return p.fontColor
}

func (p *PineappleParameter) SetFontColor(color color.Color) {
p.fontColor = color
}
17 changes: 17 additions & 0 deletions structure/layout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ func (p *Position) BaseY() float64 {
return p.y
}

type Anchor struct {
ax float64
ay float64
}

func NewAnchor(ax, ay float64) Anchor {
return Anchor{ax: ax, ay: ay}
}

func (p *Anchor) Ax() float64 {
return p.ax
}

func (p *Anchor) Ay() float64 {
return p.ay
}

type Padding struct {
left float64
top float64
Expand Down
Loading

0 comments on commit 53710d1

Please sign in to comment.