Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support pineapple style #3

Merged
merged 2 commits into from
Jul 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading