Restored accidental file misplacement
This commit is contained in:
parent
ed3dfa1ed5
commit
3f6d1840c5
|
|
@ -129,10 +129,10 @@ func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, l
|
|||
wg.Wait()
|
||||
fmt.Printf(" \r")
|
||||
|
||||
// Save ascii art as .gif image before displaying it, if --save-gif flag is passed
|
||||
// Save ascii art as .gif file before displaying it, if --save-gif flag is passed
|
||||
if saveGifPath != "" {
|
||||
|
||||
// Storing save path string before executing the concurrent ascii art to gif conversion
|
||||
// Storing save path string before executing ascii art to gif conversion
|
||||
// This is done to avoid wasting time for invalid path errors
|
||||
|
||||
saveFileName, err := createSaveFileName(gifPath, urlImgName, "-ascii-art.gif")
|
||||
|
|
|
|||
|
|
@ -78,11 +78,12 @@ func createGifFrameToSave(asciiArt [][]imgManip.AsciiChar, img image.Image, colo
|
|||
|
||||
dc := gg.NewContext(x, y)
|
||||
|
||||
dc.DrawImage(tempImg, 0, 0)
|
||||
// Set image background as black
|
||||
dc.SetRGBA(0, 0, 0, 0)
|
||||
dc.SetRGB(0, 0, 0)
|
||||
dc.Clear()
|
||||
|
||||
dc.DrawImage(tempImg, 0, 0)
|
||||
|
||||
// Load embedded font
|
||||
tempFont, err := truetype.Parse(embeddedFontFile)
|
||||
if err != nil {
|
||||
|
|
|
|||
252
convert_gif.go
252
convert_gif.go
|
|
@ -1,252 +0,0 @@
|
|||
/*
|
||||
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package aic_package
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"image"
|
||||
"image/color/palette"
|
||||
"image/draw"
|
||||
"image/gif"
|
||||
"os"
|
||||
"runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
|
||||
)
|
||||
|
||||
type GifFrame struct {
|
||||
asciiCharSet [][]imgManip.AsciiChar
|
||||
delay int
|
||||
}
|
||||
|
||||
/*
|
||||
This function grabs each image frame from passed gif and turns it into ascii art. If SaveGifPath flag is passed,
|
||||
it'll turn each ascii art into an image instance of the same dimensions as the original gif and save them
|
||||
as an ascii art gif.
|
||||
|
||||
Multi-threading has been implemented in multiple places due to long execution time
|
||||
*/
|
||||
func pathIsGif(gifPath, urlImgName string, pathIsURl bool, urlImgBytes []byte, localGif *os.File) (string, error) {
|
||||
|
||||
var (
|
||||
originalGif *gif.GIF
|
||||
err error
|
||||
)
|
||||
|
||||
if pathIsURl {
|
||||
originalGif, err = gif.DecodeAll(bytes.NewReader(urlImgBytes))
|
||||
} else {
|
||||
originalGif, err = gif.DecodeAll(localGif)
|
||||
}
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't decode %v: %v", gifPath, err)
|
||||
}
|
||||
|
||||
var (
|
||||
asciiArtSet = make([]string, len(originalGif.Image))
|
||||
gifFramesSlice = make([]GifFrame, len(originalGif.Image))
|
||||
|
||||
counter = 0
|
||||
concurrentProcesses = 0
|
||||
wg sync.WaitGroup
|
||||
hostCpuCount = runtime.NumCPU()
|
||||
)
|
||||
|
||||
fmt.Printf("Generating ascii art... 0%%\r")
|
||||
|
||||
// Get first frame of gif and its dimensions
|
||||
firstGifFrame := originalGif.Image[0].SubImage(originalGif.Image[0].Rect)
|
||||
firstGifFrameWidth := firstGifFrame.Bounds().Dx()
|
||||
firstGifFrameHeight := firstGifFrame.Bounds().Dy()
|
||||
|
||||
// Multi-threaded loop to decrease execution time
|
||||
for i, frame := range originalGif.Image {
|
||||
|
||||
wg.Add(1)
|
||||
concurrentProcesses++
|
||||
|
||||
go func(i int, frame *image.Paletted) {
|
||||
|
||||
frameImage := frame.SubImage(frame.Rect)
|
||||
|
||||
// If a frame is found that is smaller than the first frame, then this gif contains smaller subimages that are
|
||||
// positioned inside the original gif. This behavior isn't supported by this app
|
||||
if firstGifFrameWidth != frameImage.Bounds().Dx() || firstGifFrameHeight != frameImage.Bounds().Dy() {
|
||||
fmt.Println("Error: GIF contains subimages smaller than default width and height\nProcess aborted because ascii-image-converter doesn't support subimage placement and transparency in GIFs\n")
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
var imgSet [][]imgManip.AsciiPixel
|
||||
|
||||
imgSet, err = imgManip.ConvertToAsciiPixels(frameImage, dimensions, flipX, flipY, full)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
asciiCharSet := imgManip.ConvertToAscii(imgSet, negative, colored, complex, customMap)
|
||||
gifFramesSlice[i].asciiCharSet = asciiCharSet
|
||||
gifFramesSlice[i].delay = originalGif.Delay[i]
|
||||
|
||||
ascii := flattenAscii(asciiCharSet, colored)
|
||||
|
||||
asciiArtSet[i] = strings.Join(ascii, "\n")
|
||||
|
||||
counter++
|
||||
percentage := int((float64(counter) / float64(len(originalGif.Image))) * 100)
|
||||
fmt.Printf("Generating ascii art... " + strconv.Itoa(percentage) + "%%\r")
|
||||
|
||||
wg.Done()
|
||||
|
||||
}(i, frame)
|
||||
|
||||
// Limit concurrent processes according to host's CPU count to avoid overwhelming memory
|
||||
if concurrentProcesses == hostCpuCount {
|
||||
wg.Wait()
|
||||
concurrentProcesses = 0
|
||||
}
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
fmt.Printf(" \r")
|
||||
|
||||
// Save ascii art as .gif file before displaying it, if --save-gif flag is passed
|
||||
if saveGifPath != "" {
|
||||
|
||||
// Storing save path string before executing ascii art to gif conversion
|
||||
// This is done to avoid wasting time for invalid path errors
|
||||
|
||||
saveFileName, err := createSaveFileName(gifPath, urlImgName, "-ascii-art.gif")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
fullPathName, err := getFullSavePath(saveFileName, saveGifPath)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't save file: %v", err)
|
||||
}
|
||||
|
||||
// Initializing some constants for gif. Done outside loop to save execution
|
||||
outGif := &gif.GIF{
|
||||
LoopCount: originalGif.LoopCount,
|
||||
}
|
||||
opts := gif.Options{
|
||||
NumColors: 256,
|
||||
Drawer: draw.FloydSteinberg,
|
||||
}
|
||||
|
||||
// Initializing slices for each ascii art image as well as delay
|
||||
var (
|
||||
palettedImageSlice = make([]*image.Paletted, len(gifFramesSlice))
|
||||
delaySlice = make([]int, len(gifFramesSlice))
|
||||
)
|
||||
|
||||
// For the purpose of displaying counter and limiting concurrent processes
|
||||
counter = 0
|
||||
concurrentProcesses = 0
|
||||
|
||||
fmt.Printf("Saving gif... 0%%\r")
|
||||
|
||||
// Multi-threaded loop to decrease execution time
|
||||
for i, gifFrame := range gifFramesSlice {
|
||||
|
||||
wg.Add(1)
|
||||
concurrentProcesses++
|
||||
|
||||
go func(i int, gifFrame GifFrame) {
|
||||
|
||||
img := originalGif.Image[i].SubImage(originalGif.Image[i].Rect)
|
||||
|
||||
tempImg, err := createGifFrameToSave(
|
||||
gifFrame.asciiCharSet,
|
||||
img,
|
||||
colored,
|
||||
)
|
||||
if err != nil {
|
||||
fmt.Println(err)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
// Following code takes tempImg as image.Image instance and converts it into *image.Paletted instance
|
||||
b := tempImg.Bounds()
|
||||
|
||||
palettedImg := image.NewPaletted(b, palette.Plan9[:opts.NumColors])
|
||||
|
||||
opts.Drawer.Draw(palettedImg, b, tempImg, image.Point{})
|
||||
|
||||
palettedImageSlice[i] = palettedImg
|
||||
delaySlice[i] = gifFrame.delay
|
||||
|
||||
counter++
|
||||
percentage := int((float64(counter) / float64(len(gifFramesSlice))) * 100)
|
||||
fmt.Printf("Saving gif... " + strconv.Itoa(percentage) + "%%\r")
|
||||
|
||||
wg.Done()
|
||||
|
||||
}(i, gifFrame)
|
||||
|
||||
// Limit concurrent processes according to host's CPU count to avoid overwhelming memory
|
||||
if concurrentProcesses == hostCpuCount {
|
||||
wg.Wait()
|
||||
concurrentProcesses = 0
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
wg.Wait()
|
||||
|
||||
outGif.Image = palettedImageSlice
|
||||
outGif.Delay = delaySlice
|
||||
|
||||
gifFile, err := os.OpenFile(fullPathName, os.O_WRONLY|os.O_CREATE, 0666)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("can't save file: %v", err)
|
||||
}
|
||||
defer gifFile.Close()
|
||||
|
||||
gif.EncodeAll(gifFile, outGif)
|
||||
|
||||
fmt.Printf(" \r")
|
||||
}
|
||||
|
||||
// Display the gif
|
||||
loopCount := 0
|
||||
for {
|
||||
for i, asciiFrame := range asciiArtSet {
|
||||
clearScreen()
|
||||
fmt.Println(asciiFrame)
|
||||
time.Sleep(time.Duration((time.Second * time.Duration(originalGif.Delay[i])) / 100))
|
||||
}
|
||||
|
||||
// If gif is infinite loop
|
||||
if originalGif.LoopCount == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
loopCount++
|
||||
if loopCount == originalGif.LoopCount {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
/*
|
||||
Copyright © 2021 Zoraiz Hassan <hzoraiz8@gmail.com>
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package aic_package
|
||||
|
||||
import (
|
||||
"image"
|
||||
"image/color"
|
||||
|
||||
_ "embed"
|
||||
|
||||
imgManip "github.com/TheZoraiz/ascii-image-converter/image_manipulation"
|
||||
"github.com/golang/freetype/truetype"
|
||||
|
||||
"github.com/fogleman/gg"
|
||||
)
|
||||
|
||||
/*
|
||||
Unlike createImageToSave(), this function is optimized to maintain original image dimensions and shrink ascii
|
||||
art font size to match it. This allows for greater execution speed, which is necessary since a gif contains
|
||||
multiple images that need to be converted to ascii art, and the potential loss of ascii art quality (since
|
||||
large ascii art instances will shrink the font too much).
|
||||
|
||||
Furthermore, maintaining original gif's width and height also allows for gifs of smaller size.
|
||||
*/
|
||||
func createGifFrameToSave(asciiArt [][]imgManip.AsciiChar, img image.Image, colored bool) (image.Image, error) {
|
||||
|
||||
// Original image dimensions
|
||||
x := img.Bounds().Dx()
|
||||
y := img.Bounds().Dy()
|
||||
|
||||
// Ascii art dimensions
|
||||
asciiWidth := len(asciiArt[0])
|
||||
asciiHeight := len(asciiArt)
|
||||
|
||||
// Iterators to move pointer on the image to be made
|
||||
var xIter float64
|
||||
var yIter float64
|
||||
|
||||
var fontSize float64
|
||||
|
||||
// Conditions to alter resulting ascii gif dimensions according to ascii art dimensions
|
||||
if asciiWidth > asciiHeight*2 {
|
||||
yIter = float64(y) / float64(asciiHeight)
|
||||
|
||||
xIter = yIter / 2
|
||||
x = int(xIter * float64(asciiWidth))
|
||||
|
||||
fontSize = xIter
|
||||
|
||||
} else {
|
||||
xIter = float64(x) / float64(asciiWidth)
|
||||
|
||||
yIter = xIter * 2
|
||||
y = int(yIter * float64(asciiHeight))
|
||||
|
||||
fontSize = xIter
|
||||
}
|
||||
|
||||
// 10 extra pixels on both x and y-axis to have 5 pixels of padding on each side
|
||||
x += 10
|
||||
y += 10
|
||||
|
||||
tempImg := image.NewRGBA(image.Rect(0, 0, x, y))
|
||||
|
||||
dc := gg.NewContext(x, y)
|
||||
|
||||
// Set image background as black
|
||||
dc.SetRGB(0, 0, 0)
|
||||
dc.Clear()
|
||||
|
||||
dc.DrawImage(tempImg, 0, 0)
|
||||
|
||||
// Load embedded font
|
||||
tempFont, err := truetype.Parse(embeddedFontFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Font size increased during assignment to become more visible. This will not affect image drawing
|
||||
robotoBoldFontFace := truetype.NewFace(tempFont, &truetype.Options{Size: fontSize * 1.5})
|
||||
|
||||
dc.SetFontFace(robotoBoldFontFace)
|
||||
|
||||
// Font color of text on picture is white by default
|
||||
dc.SetColor(color.White)
|
||||
|
||||
// Pointer to track y-axis on the image frame
|
||||
yImgPointer := 5.0
|
||||
|
||||
// These nested loops print each character in asciArt 2D slice separately
|
||||
// so that their RGB colors can be maintained in the resulting image
|
||||
for _, line := range asciiArt {
|
||||
|
||||
// Pointer to track x-axis on the image frame
|
||||
xImgPointer := 5.0
|
||||
|
||||
for _, char := range line {
|
||||
|
||||
r := uint8(char.RgbValue[0])
|
||||
g := uint8(char.RgbValue[1])
|
||||
b := uint8(char.RgbValue[2])
|
||||
|
||||
if colored {
|
||||
// Simple put, dc.SetColor() sets color for EACH character before printing it
|
||||
dc.SetColor(color.RGBA{r, g, b, 255})
|
||||
}
|
||||
|
||||
dc.DrawStringWrapped(char.Simple, xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
|
||||
|
||||
// Incremet x-axis pointer character so new one can be printed after it
|
||||
// Set to the same constant as in line
|
||||
xImgPointer += xIter
|
||||
}
|
||||
|
||||
dc.DrawStringWrapped("\n", xImgPointer, yImgPointer, 0, 0, float64(x), 1.8, gg.AlignLeft)
|
||||
|
||||
// Incremet pointer for y axis after every line printed, so
|
||||
// new line can start at below the previous one on next iteration
|
||||
yImgPointer += yIter
|
||||
}
|
||||
|
||||
return dc.Image(), nil
|
||||
}
|
||||
Loading…
Reference in New Issue