You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
352 lines
7.4 KiB
Go
352 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"image"
|
|
"image/color"
|
|
"path/filepath"
|
|
"strings"
|
|
"tildegit.org/sloum/filtress/parser"
|
|
)
|
|
|
|
type point struct {
|
|
x int
|
|
y int
|
|
maxX int
|
|
maxY int
|
|
}
|
|
|
|
type filter struct {
|
|
max int
|
|
min int
|
|
val int
|
|
}
|
|
|
|
type col struct {
|
|
red filter
|
|
green filter
|
|
blue filter
|
|
alpha filter
|
|
}
|
|
|
|
type filterState struct {
|
|
red filter
|
|
green filter
|
|
blue filter
|
|
alpha filter
|
|
location point
|
|
mode filter
|
|
}
|
|
var variables = map[string]int{}
|
|
var tree parser.AST
|
|
var fil filterState
|
|
var im *image.RGBA
|
|
var imFormat string
|
|
|
|
func openFileFromPath(path string) *os.File {
|
|
file, err := os.Open(path)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
return file
|
|
}
|
|
|
|
func generateAST(path string) {
|
|
filterFile := openFileFromPath(path)
|
|
defer filterFile.Close()
|
|
parser := parser.NewParser(filterFile)
|
|
tree = parser.Parse()
|
|
}
|
|
|
|
func validateInput(path string) {
|
|
generateAST(path)
|
|
if len(tree.Procedures) > 0 {
|
|
fmt.Printf("File has %d procedures\n", len(tree.Procedures))
|
|
fmt.Println("File is valid")
|
|
} else {
|
|
fmt.Println("Invalid or empty input file")
|
|
}
|
|
|
|
os.Exit(0)
|
|
}
|
|
|
|
func filterImage(path, fname string) {
|
|
fmt.Print("Loading image... ")
|
|
im, imFormat = loadImage(path)
|
|
imSize := im.Bounds().Size()
|
|
variables["RG1"] = 0
|
|
variables["RG2"] = 0
|
|
fil.location.maxX = imSize.X
|
|
fil.location.maxY = imSize.Y
|
|
|
|
fil = filterState{
|
|
filter{255, 0, 255},
|
|
filter{255, 0,255},
|
|
filter{255, 0,255},
|
|
filter{255, 0,255},
|
|
point{0,0,imSize.X,imSize.Y},
|
|
filter{3,0,0},
|
|
}
|
|
|
|
fmt.Print("Filtering image... ")
|
|
for _, proc := range tree.Procedures {
|
|
procedure(proc)
|
|
}
|
|
fmt.Print("Saving image... \n")
|
|
saveImage(path, fname, imFormat, im)
|
|
fmt.Print("Image saved. Done. \n")
|
|
}
|
|
|
|
func updateRegister(reg string, exp parser.Expression) {
|
|
var err error
|
|
opand := exp.Opperand
|
|
if exp.Variable != "" {
|
|
opand, err = retrieveVariableValue(exp.Variable)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Runtime error: Unknown variable %q encountered", exp.Variable))
|
|
}
|
|
}
|
|
switch exp.Opperator {
|
|
case '+':
|
|
variables[reg] += opand
|
|
case '-':
|
|
variables[reg] -= opand
|
|
case '*':
|
|
variables[reg] *= opand
|
|
case '/':
|
|
if opand == 0 {
|
|
variables[reg] = 1
|
|
} else {
|
|
variables[reg] /= opand
|
|
}
|
|
case '=':
|
|
variables[reg] = opand
|
|
}
|
|
}
|
|
|
|
func retrieveVariableValue(ident string) (int, error) {
|
|
var val int
|
|
switch ident {
|
|
case "RG1", "RG2":
|
|
val = variables[ident]
|
|
case "WID":
|
|
val = fil.location.maxX
|
|
case "HIG":
|
|
val = fil.location.maxY
|
|
case "RED":
|
|
val = fil.red.val
|
|
case "GRN":
|
|
val = fil.green.val
|
|
case "BLU":
|
|
val = fil.blue.val
|
|
case "APH":
|
|
val = fil.alpha.val
|
|
case "LOX":
|
|
val = fil.location.x
|
|
case "LOY":
|
|
val = fil.location.y
|
|
default:
|
|
return 0, fmt.Errorf("Unknown variable %q sent to retrieveVariableValue", ident)
|
|
}
|
|
|
|
return val, nil
|
|
}
|
|
|
|
func procedure(p parser.Procedure) {
|
|
switch p.Kind {
|
|
case "LOC":
|
|
if len(p.Expressions) == 2 {
|
|
fil.location.update(p.Expressions)
|
|
}
|
|
case "LOX":
|
|
if len(p.Expressions) == 1 {
|
|
y := parser.Expression{Opperator: '+', Opperand: 0, Variable: ""}
|
|
p.Expressions = append(p.Expressions, y)
|
|
fil.location.update(p.Expressions)
|
|
}
|
|
case "LOY":
|
|
if len(p.Expressions) == 1 {
|
|
x := parser.Expression{Opperator: '+', Opperand: 0, Variable: ""}
|
|
exps := make([]parser.Expression,0,2)
|
|
exps = append(exps, x, p.Expressions[0])
|
|
fil.location.update(exps)
|
|
}
|
|
case "MOD":
|
|
if len(p.Expressions) >= 1 {
|
|
fil.mode.update(p.Expressions[0])
|
|
}
|
|
case "RG1", "RG2":
|
|
if len(p.Expressions) >= 1 {
|
|
updateRegister(p.Kind, p.Expressions[0])
|
|
}
|
|
case "APY":
|
|
applyToImage(p.Expressions)
|
|
case "RED":
|
|
if len(p.Expressions) >= 1 {
|
|
fil.red.update(p.Expressions[0])
|
|
}
|
|
case "BLU":
|
|
if len(p.Expressions) >= 1 {
|
|
fil.blue.update(p.Expressions[0])
|
|
}
|
|
case "GRN":
|
|
if len(p.Expressions) >= 1 {
|
|
fil.green.update(p.Expressions[0])
|
|
}
|
|
case "APH":
|
|
if len(p.Expressions) >= 1 {
|
|
fil.alpha.update(p.Expressions[0])
|
|
}
|
|
case "COL":
|
|
if len(p.Expressions) == 4 {
|
|
fil.red.update(p.Expressions[0])
|
|
fil.green.update(p.Expressions[1])
|
|
fil.blue.update(p.Expressions[2])
|
|
fil.alpha.update(p.Expressions[3])
|
|
}
|
|
case "GET":
|
|
fil.getColors()
|
|
}
|
|
}
|
|
|
|
func applyToImage(exps []parser.Expression) {
|
|
var r,g,b,a int
|
|
var col color.RGBA
|
|
var err error
|
|
if len(exps) == 0 {
|
|
r = fil.red.val
|
|
g = fil.green.val
|
|
b = fil.blue.val
|
|
a = fil.alpha.val
|
|
if fil.mode.val != 0 {
|
|
col = im.RGBAAt(fil.location.x, fil.location.y)
|
|
r = avgColor(int(col.R), r)
|
|
g = avgColor(int(col.G), g)
|
|
b = avgColor(int(col.B), b)
|
|
a = avgColor(int(col.A), a)
|
|
}
|
|
|
|
im.SetRGBA(
|
|
fil.location.x,
|
|
fil.location.y,
|
|
color.RGBA{
|
|
uint8(r),
|
|
uint8(g),
|
|
uint8(b),
|
|
uint8(a),
|
|
})
|
|
} else if len(exps) == 2 {
|
|
endX := exps[0].Opperand
|
|
endY := exps[1].Opperand
|
|
if exps[0].Variable != "" {
|
|
endX, err = retrieveVariableValue(exps[0].Variable)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Runtime error: Unknown variable %q encountered", exps[0].Variable))
|
|
}
|
|
}
|
|
if exps[1].Variable != "" {
|
|
endY, err = retrieveVariableValue(exps[1].Variable)
|
|
if err != nil {
|
|
panic(fmt.Sprintf("Runtime error: Unknown variable %q encountered", exps[1].Variable))
|
|
}
|
|
}
|
|
|
|
endX = getBoxBound(fil.location.x, endX, fil.location.maxX, exps[0].Opperator)
|
|
endY = getBoxBound(fil.location.y, endY, fil.location.maxY, exps[1].Opperator)
|
|
|
|
for y := fil.location.y; y <= endY; y++ {
|
|
for x := fil.location.x; x <= endX; x++ {
|
|
r = fil.red.val
|
|
g = fil.green.val
|
|
b = fil.blue.val
|
|
a = fil.alpha.val
|
|
if fil.mode.val != 0 {
|
|
col = im.RGBAAt(x, y)
|
|
r = avgColor(int(col.R), r)
|
|
g = avgColor(int(col.G), g)
|
|
b = avgColor(int(col.B), b)
|
|
a = avgColor(int(col.A), a)
|
|
}
|
|
|
|
im.SetRGBA(
|
|
x,
|
|
y,
|
|
color.RGBA{
|
|
uint8(r),
|
|
uint8(g),
|
|
uint8(b),
|
|
uint8(a),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func getBoxBound(start, end, max int, op rune) int {
|
|
var out int
|
|
switch op {
|
|
case '+':
|
|
out = start + end
|
|
case '-':
|
|
out = start - end
|
|
case '*':
|
|
out = start * end
|
|
case '/':
|
|
if end == 0 {
|
|
out = 0
|
|
} else {
|
|
out = start / end
|
|
}
|
|
case '%':
|
|
if end == 0 {
|
|
out = 0
|
|
} else {
|
|
out = start % end
|
|
}
|
|
case '=':
|
|
out = start
|
|
}
|
|
if out > max {
|
|
out = max
|
|
} else if out < 0 {
|
|
out = 0
|
|
}
|
|
return out
|
|
}
|
|
|
|
|
|
func main() {
|
|
validateFlag := flag.Bool("v", false, "If present, will validate the provided frs file and exit")
|
|
inputFlag := flag.String("image", "", "Path to the image to be filtered")
|
|
flag.Parse()
|
|
posArgs := flag.Args()
|
|
if len(posArgs) > 1 || len(posArgs) < 1 {
|
|
panic(fmt.Sprintf("Input error: Too many positional arguments. Expected one, received %d", len(flag.Args())))
|
|
}
|
|
fpath := posArgs[0]
|
|
fext := strings.ToUpper(filepath.Ext(fpath))
|
|
_, fname := filepath.Split(fpath)
|
|
|
|
if fext != ".FRS" {
|
|
panic(fmt.Sprintf("Input error: Incorrect filetype. Expected .FRS, received \".%s\"", fext))
|
|
}
|
|
|
|
if *validateFlag {
|
|
fmt.Printf("Validating file: %s ...\n", fname)
|
|
validateInput(fpath)
|
|
}
|
|
|
|
if *inputFlag == "" {
|
|
fmt.Println("Input error: No image file was provided to filtress")
|
|
os.Exit(1)
|
|
}
|
|
|
|
fmt.Printf("Parsing file: %s ...\n", fname)
|
|
generateAST(fpath)
|
|
frsName := strings.Split(fname, ".")[0]
|
|
filterImage(*inputFlag, frsName)
|
|
}
|