-
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.
Merge pull request #3 from sincerefly/feature/style-pineapple
feat: support pineapple style
- Loading branch information
Showing
13 changed files
with
359 additions
and
0 deletions.
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
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
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,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 not shown.
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
Binary file not shown.
Binary file not shown.
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
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,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 | ||
} |
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,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 | ||
} |
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
Oops, something went wrong.