image_filter_dsl 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/image_filter_dsl +10 -0
- data/lib/image_filter_dsl.rb +47 -0
- data/lib/image_filter_dsl/binary/serialize.rb +175 -0
- data/lib/image_filter_dsl/binary/struct.rb +98 -0
- data/lib/image_filter_dsl/dsl/filter.rb +47 -0
- data/lib/image_filter_dsl/dsl/filter_instructions.rb +225 -0
- data/lib/image_filter_dsl/dsl/kernel.rb +85 -0
- data/lib/image_filter_dsl/engine/image_processor.rb +199 -0
- data/lib/image_filter_dsl/engine/io.rb +54 -0
- metadata +54 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: dad7597cc47d2d15fbba4c15de8222bd7fd60599709c34801d909ba1b8c4e9e3
|
|
4
|
+
data.tar.gz: 53dc7843ef41747dc96baac57a440da580821c6b1ff18e3ecad479846343c51d
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 18274358415905e80d5969243832d30847496bc219510d3a11d5fd88be347c7a5de70eeb8415095413c684999beb24a269f5137b3a88c58bb223b6e78fb05a20
|
|
7
|
+
data.tar.gz: 61fcf052a2afb9f8445fcda605c6e94a84007680d55c5bda9bff4b995f5ff0854f3ba674b072a99da10f6ba037b5991b8308d563f270e73545d0ee95610908d1
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
require_relative './image_filter_dsl/dsl/filter.rb'
|
|
2
|
+
require_relative './image_filter_dsl/dsl/filter_instructions.rb'
|
|
3
|
+
require_relative './image_filter_dsl/dsl/kernel.rb'
|
|
4
|
+
require_relative './image_filter_dsl/binary/struct.rb'
|
|
5
|
+
require_relative './image_filter_dsl/binary/serialize.rb'
|
|
6
|
+
require_relative './image_filter_dsl/engine/io.rb'
|
|
7
|
+
require_relative './image_filter_dsl/engine/image_processor.rb'
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# Image Filter DSL Library
|
|
11
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
12
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
13
|
+
module ImageFilterDsl
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Reference to Filter module
|
|
17
|
+
Filter = Dsl::Filter
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Shortcut to ImageProcessor constructor
|
|
21
|
+
# @param [Dsl::Kernel::FilterKernel|String] kernel FilterKernel object or path to binary kernel file
|
|
22
|
+
# @param [Integer] threads How many threads for processor to use
|
|
23
|
+
# @return [Engine::ImageProcessor] new instance of image processor
|
|
24
|
+
def self.image_processor(kernel, threads=6)
|
|
25
|
+
Engine::ImageProcessor.new(kernel, threads)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Execute process image using kernel from CLI
|
|
30
|
+
# @param [Array] args Array of arguments (kernel file, img in, img out)
|
|
31
|
+
def self.cli_process_image(args)
|
|
32
|
+
kernel_file = args[0]
|
|
33
|
+
img_in = args[1]
|
|
34
|
+
img_out = args[2]
|
|
35
|
+
ip = image_processor(kernel_file)
|
|
36
|
+
ip.process_image(img_in, img_out)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
##
|
|
40
|
+
# Shorthand for Engine::IO.write
|
|
41
|
+
# @param [Dsl::Kernel::FilterKernel] filter Filter to write
|
|
42
|
+
# @param [String] filename Filename to write to
|
|
43
|
+
def self.save_binary_kernel(filter, filename)
|
|
44
|
+
Engine::IO.write(filename, filter)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Image Filter DSL Library
|
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
5
|
+
module ImageFilterDsl
|
|
6
|
+
module Binary
|
|
7
|
+
##
|
|
8
|
+
# Module providing serialization functionality for converting between
|
|
9
|
+
# FilterKernel instance and binary IfdKernel record
|
|
10
|
+
module Serialize
|
|
11
|
+
|
|
12
|
+
# @!group Object -> Binary
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# Alias for Serialize::from_kernel
|
|
16
|
+
def self.to_record(filter)
|
|
17
|
+
from_kernel(filter)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Build binary IfdKernel record from Kernel object
|
|
22
|
+
# @param [Dsl::Kernel::FilterKernel] filter Source filter kernel
|
|
23
|
+
# @return [Binary::Struct::IfdKernel] Binary kernel record
|
|
24
|
+
def self.from_kernel(filter)
|
|
25
|
+
variables = {
|
|
26
|
+
inputs: nil,
|
|
27
|
+
outputs: nil,
|
|
28
|
+
pool: nil,
|
|
29
|
+
symbols: nil
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
parts = {}
|
|
33
|
+
|
|
34
|
+
# Collect variables
|
|
35
|
+
pool = (filter.inputs + filter.outputs).uniq
|
|
36
|
+
filter.instructions.each do |i|
|
|
37
|
+
pool += i.inputs
|
|
38
|
+
pool += [i.out]
|
|
39
|
+
pool.uniq!
|
|
40
|
+
end
|
|
41
|
+
variables[:pool] = pool
|
|
42
|
+
|
|
43
|
+
# Generate variable symbols
|
|
44
|
+
variables[:symbols] = {}
|
|
45
|
+
count = 1
|
|
46
|
+
variables[:pool].each do |v|
|
|
47
|
+
variables[:symbols][v] = count
|
|
48
|
+
count += 1
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Build variables record
|
|
52
|
+
|
|
53
|
+
vars_rec = Binary::Struct::IfdVariables.new(
|
|
54
|
+
field_count: variables[:pool].length,
|
|
55
|
+
input_count: filter.inputs.length,
|
|
56
|
+
output_count: filter.outputs.length,
|
|
57
|
+
input_fields: filter.inputs.map { |i| variables[:symbols][i] },
|
|
58
|
+
output_fields: filter.outputs.map { |i| variables[:symbols][i] }
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
fkinds = Binary::Struct::FIELD_KINDS
|
|
62
|
+
|
|
63
|
+
defs = variables[:symbols].keys.map do |k|
|
|
64
|
+
f_field = Binary::Struct::IfdField.new(
|
|
65
|
+
symbol: variables[:symbols][k]
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
if k.is_a?(Symbol)
|
|
69
|
+
f_field.kind = fkinds[:var]
|
|
70
|
+
f_field.name_length = k.to_s.length
|
|
71
|
+
f_field.variable.assign(k.to_s)
|
|
72
|
+
elsif k.is_a?(Integer)
|
|
73
|
+
f_field.kind = fkinds[:literal_int]
|
|
74
|
+
f_field.int_value = k
|
|
75
|
+
else
|
|
76
|
+
f_field.kind = fkinds[:literal_float]
|
|
77
|
+
f_field.float_value = k
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
f_field
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
vars_rec.definition = defs
|
|
84
|
+
parts[:variables] = vars_rec
|
|
85
|
+
|
|
86
|
+
# Build instructions records
|
|
87
|
+
parts[:instructions] = filter.instructions.map do |i|
|
|
88
|
+
inp_fields = i.inputs.map { |f| variables[:symbols][f] }
|
|
89
|
+
f_ins = Binary::Struct::IfdInstruction.new(
|
|
90
|
+
operation: Binary::Struct.instruction_symbol(i.op),
|
|
91
|
+
input_count: i.inputs.length,
|
|
92
|
+
input_fields: inp_fields,
|
|
93
|
+
output_field: variables[:symbols][i.out]
|
|
94
|
+
)
|
|
95
|
+
f_ins
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Build Kernel Record
|
|
99
|
+
kernel = Binary::Struct::IfdKernel.new(
|
|
100
|
+
header: Binary::Struct::IfdHeader.new,
|
|
101
|
+
variables: parts[:variables],
|
|
102
|
+
instruction_count: filter.instructions.length,
|
|
103
|
+
instructions: parts[:instructions]
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return kernel
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# @!endgroup
|
|
110
|
+
|
|
111
|
+
# @!group Binary -> Object
|
|
112
|
+
|
|
113
|
+
##
|
|
114
|
+
# Alias for Serialize::from_record
|
|
115
|
+
def self.to_kernel(record)
|
|
116
|
+
from_record(record)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
##
|
|
120
|
+
# Convert IfdKernel record to FilterKernel object
|
|
121
|
+
# @param [Binary::Struct::IfdKernel] record Record to convert
|
|
122
|
+
# @return [Dsl::Kernel::FilterKernel] FilterKernel from record
|
|
123
|
+
def self.from_record(record)
|
|
124
|
+
ins = []
|
|
125
|
+
outs = []
|
|
126
|
+
|
|
127
|
+
vars = record.variables
|
|
128
|
+
vardefs = vars.definition
|
|
129
|
+
|
|
130
|
+
vars_val = Proc.new do |vs,force_string=false|
|
|
131
|
+
r = nil
|
|
132
|
+
i = vardefs.select{|v| v.symbol == vs}[0]
|
|
133
|
+
if i.kind == Binary::Struct::FIELD_KINDS[:var]
|
|
134
|
+
r = i.variable.strip.to_sym
|
|
135
|
+
r = ":#{r.to_s}" if force_string
|
|
136
|
+
elsif i.kind == Binary::Struct::FIELD_KINDS[:literal_int]
|
|
137
|
+
r = i.int_value
|
|
138
|
+
else
|
|
139
|
+
r = i.float_value
|
|
140
|
+
end
|
|
141
|
+
r
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
op_sym = Proc.new do |opc|
|
|
145
|
+
Dsl::FilterInstructions::OP_INS.select{|k,v| v==opc}.keys[0]
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
ins = vars.input_fields.map { |f|
|
|
149
|
+
vardefs.select{|v| v.symbol == f}[0].variable.strip.to_sym
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
outs = vars.output_fields.map { |f|
|
|
153
|
+
vardefs.select{|v| v.symbol == f}[0].variable.strip.to_sym
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
instructions = record.instructions.map do |i|
|
|
157
|
+
[
|
|
158
|
+
"#{op_sym.call(i.operation)} ",
|
|
159
|
+
i.input_fields.map{|f| vars_val.call(f) }.to_s,
|
|
160
|
+
" , ", vars_val.call(i.output_field, true).to_s
|
|
161
|
+
].join("")
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
f = Dsl::Filter.define(ins, outs){
|
|
165
|
+
self.instance_eval(instructions.join("\n"))
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return f
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# @!endgroup
|
|
172
|
+
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
require 'bindata'
|
|
2
|
+
##
|
|
3
|
+
# Image Filter DSL Library
|
|
4
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
5
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
6
|
+
module ImageFilterDsl
|
|
7
|
+
module Binary
|
|
8
|
+
##
|
|
9
|
+
# Data structures used for storing Filter Kernels
|
|
10
|
+
# in binary format
|
|
11
|
+
module Struct
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Symbols used to indicate kind of a field
|
|
15
|
+
FIELD_KINDS = {
|
|
16
|
+
var: 0x1a,
|
|
17
|
+
literal_int: 0x12,
|
|
18
|
+
literal_float: 0x1f
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Constants used in header
|
|
23
|
+
HEADER_VALUES = {
|
|
24
|
+
header: "ifdKernel",
|
|
25
|
+
version: 0.01
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Look up hex symbol for instruction in FilterInstructions module
|
|
30
|
+
# @param [Symbol] ins Instruction to look up
|
|
31
|
+
# @return [Integer] Instruction hex symbol
|
|
32
|
+
def self.instruction_symbol(ins)
|
|
33
|
+
Dsl::FilterInstructions::OP_INS[ins]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Image Filter DSL Field record
|
|
38
|
+
class IfdField < BinData::Record
|
|
39
|
+
uint16le :symbol
|
|
40
|
+
uint16le :kind
|
|
41
|
+
uint8le :name_length
|
|
42
|
+
string :variable, :length => :name_length,
|
|
43
|
+
:onlyif => lambda { kind == FIELD_KINDS[:var] }
|
|
44
|
+
int32le :int_value,
|
|
45
|
+
:onlyif => lambda { kind == FIELD_KINDS[:literal_int] }
|
|
46
|
+
float_le :float_value,
|
|
47
|
+
:onlyif => lambda { kind == FIELD_KINDS[:literal_float] }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Image Filter DSL Instruction record
|
|
52
|
+
class IfdInstruction < BinData::Record
|
|
53
|
+
uint16le :operation
|
|
54
|
+
uint8le :input_count
|
|
55
|
+
array :input_fields, :initial_length => :input_count do
|
|
56
|
+
uint16le :symbol
|
|
57
|
+
end
|
|
58
|
+
uint16le :output_field
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Image Filter DSL Variables definition record
|
|
63
|
+
class IfdVariables < BinData::Record
|
|
64
|
+
uint8le :field_count
|
|
65
|
+
uint8le :input_count
|
|
66
|
+
uint8le :output_count
|
|
67
|
+
array :definition, :initial_length => :field_count do
|
|
68
|
+
ifd_field :field
|
|
69
|
+
end
|
|
70
|
+
array :input_fields, :initial_length => :input_count do
|
|
71
|
+
uint16le :field
|
|
72
|
+
end
|
|
73
|
+
array :output_fields, :initial_length => :output_count do
|
|
74
|
+
uint16le :field
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
##
|
|
79
|
+
# Image Filter DSL Header record
|
|
80
|
+
class IfdHeader < BinData::Record
|
|
81
|
+
string :header, read_length: HEADER_VALUES[:header].length,
|
|
82
|
+
value: HEADER_VALUES[:header]
|
|
83
|
+
float_le :version, value: HEADER_VALUES[:version]
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
##
|
|
87
|
+
# Main Image Filter DSL Kernel record
|
|
88
|
+
class IfdKernel < BinData::Record
|
|
89
|
+
ifd_header :header
|
|
90
|
+
ifd_variables :variables
|
|
91
|
+
uint16le :instruction_count
|
|
92
|
+
array :instructions, :initial_length => :instruction_count do
|
|
93
|
+
ifd_instruction :instruction
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Image Filter DSL Library
|
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
5
|
+
module ImageFilterDsl
|
|
6
|
+
module Dsl
|
|
7
|
+
##
|
|
8
|
+
# Module used for declaring Filter w/ DSL
|
|
9
|
+
module Filter
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Define method
|
|
13
|
+
# @param [Array] ins Input symbols
|
|
14
|
+
# @param [Array] outs Output symbols
|
|
15
|
+
# @param [Proc] &block Filter instructions body
|
|
16
|
+
# @return [FilterKernel] new Filter Kernel
|
|
17
|
+
def self.define(ins,outs,&block)
|
|
18
|
+
kernel = Kernel::FilterKernel.new(ins,outs)
|
|
19
|
+
ip = processor(kernel)
|
|
20
|
+
ip.instance_eval &block
|
|
21
|
+
return kernel
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Process filter, creating new Instruction Processing module
|
|
28
|
+
# @param [FilterKernel] kernel Target FilterKernel
|
|
29
|
+
# @return [Module] module wrapping instructions to target kernel
|
|
30
|
+
def self.processor(kernel)
|
|
31
|
+
instruction_proc = Module.new
|
|
32
|
+
|
|
33
|
+
FilterInstructions::OPS.each do |op|
|
|
34
|
+
p = Proc.new do |ins,outs|
|
|
35
|
+
kernel.store_instruction(
|
|
36
|
+
Kernel::KernelInstruction.new(op,ins,outs)
|
|
37
|
+
)
|
|
38
|
+
end
|
|
39
|
+
instruction_proc.class.send(:define_method, op, p)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
instruction_proc
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Image Filter DSL Library
|
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
5
|
+
module ImageFilterDsl
|
|
6
|
+
module Dsl
|
|
7
|
+
##
|
|
8
|
+
# Module defining filter kernel instruction logic
|
|
9
|
+
module FilterInstructions
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Hash of binary instruction symbols
|
|
13
|
+
OP_INS = {
|
|
14
|
+
# math = 0x0X-0xAF
|
|
15
|
+
add: 0x01,
|
|
16
|
+
mult: 0x02,
|
|
17
|
+
div: 0x03,
|
|
18
|
+
mod: 0x04,
|
|
19
|
+
abs: 0x05,
|
|
20
|
+
# collection op = 0xB0-0xCF
|
|
21
|
+
min: 0xb0,
|
|
22
|
+
max: 0xb1,
|
|
23
|
+
avg: 0xb2,
|
|
24
|
+
# logic/mem/conv op = 0xD0-??
|
|
25
|
+
copy: 0xd0,
|
|
26
|
+
above: 0xd1,
|
|
27
|
+
below: 0xd2,
|
|
28
|
+
floor: 0xd3,
|
|
29
|
+
ceil: 0xd4,
|
|
30
|
+
float: 0xd5,
|
|
31
|
+
round: 0xd6,
|
|
32
|
+
switch: 0xd7,
|
|
33
|
+
eq: 0xd8,
|
|
34
|
+
bnot: 0xd9
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Array of all valid filter instructions
|
|
39
|
+
OPS = OP_INS.keys
|
|
40
|
+
|
|
41
|
+
##
|
|
42
|
+
# Add instruction
|
|
43
|
+
# @param [Array] input values
|
|
44
|
+
# @return [Integer|Float] output value
|
|
45
|
+
def self.add(i)
|
|
46
|
+
i.sum
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# Multiply instruction
|
|
51
|
+
# @param [Array] input values
|
|
52
|
+
# @return [Integer|Float] output value
|
|
53
|
+
def self.mult(i)
|
|
54
|
+
v=1;
|
|
55
|
+
i.each{|n|v=v*n}
|
|
56
|
+
v
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Multiply instruction
|
|
61
|
+
# @param [Array] input values
|
|
62
|
+
# @return [Integer|Float] output value
|
|
63
|
+
def self.div(i)
|
|
64
|
+
i[0]/i[1]
|
|
65
|
+
end
|
|
66
|
+
##
|
|
67
|
+
# Calculate modulo
|
|
68
|
+
# @param [Array] input values
|
|
69
|
+
# @return [Integer|] output value
|
|
70
|
+
def self.mod(i)
|
|
71
|
+
i[0] % i[1]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Absolute value
|
|
76
|
+
# @param [Array] input value
|
|
77
|
+
# @return [Integer|Float] output value
|
|
78
|
+
def self.abs(i)
|
|
79
|
+
i[0].abs
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Minimum instruction
|
|
84
|
+
# @param [Array] input values
|
|
85
|
+
# @return [Integer|Float] output value
|
|
86
|
+
def self.min(i)
|
|
87
|
+
i.min
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
##
|
|
91
|
+
# Maximum instruction
|
|
92
|
+
# @param [Array] input values
|
|
93
|
+
# @return [Integer|Float] output value
|
|
94
|
+
def self.max(i)
|
|
95
|
+
i.max
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
##
|
|
99
|
+
# Average instruction
|
|
100
|
+
# @param [Array] input values
|
|
101
|
+
# @return [Integer|Float] output value
|
|
102
|
+
def self.avg(i)
|
|
103
|
+
i.sum / (1.0 * i.length)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
##
|
|
107
|
+
# Copy instruction
|
|
108
|
+
# @param [Array] input values (src)
|
|
109
|
+
# @return [Integer|Float] output value
|
|
110
|
+
def self.copy(i)
|
|
111
|
+
i[0]
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
##
|
|
115
|
+
# Above instruction
|
|
116
|
+
# @param [Array] input values (a,b,trueVal,falseVal)
|
|
117
|
+
# @return [Integer|Float] output value
|
|
118
|
+
def self.above(i)
|
|
119
|
+
if(i[0]>i[1])
|
|
120
|
+
if i.length < 3
|
|
121
|
+
1
|
|
122
|
+
else
|
|
123
|
+
i[2]
|
|
124
|
+
end
|
|
125
|
+
else
|
|
126
|
+
if i.length < 4
|
|
127
|
+
0
|
|
128
|
+
else
|
|
129
|
+
i[3]
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
##
|
|
135
|
+
# Below instruction
|
|
136
|
+
# @param [Array] input values (a,b,trueVal,falseVal)
|
|
137
|
+
# @return [Integer|Float] output value
|
|
138
|
+
def self.below(i)
|
|
139
|
+
if(i[0]<i[1])
|
|
140
|
+
if i.length < 3
|
|
141
|
+
1
|
|
142
|
+
else
|
|
143
|
+
i[2]
|
|
144
|
+
end
|
|
145
|
+
else
|
|
146
|
+
if i.length < 4
|
|
147
|
+
0
|
|
148
|
+
else
|
|
149
|
+
i[3]
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
##
|
|
155
|
+
# Floor instruction
|
|
156
|
+
# @param [Array] input value (v)
|
|
157
|
+
# @return [Integer|Float] output value
|
|
158
|
+
def self.floor(i)
|
|
159
|
+
i[0].floor
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
##
|
|
163
|
+
# Ceil instruction
|
|
164
|
+
# @param [Array] input value (v)
|
|
165
|
+
# @return [Integer|Float] output value
|
|
166
|
+
def self.ceil(i)
|
|
167
|
+
i[0].ceil
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
##
|
|
171
|
+
# Float cast instruction
|
|
172
|
+
# @param [Array] input value (v)
|
|
173
|
+
# @return [Integer|Float] output value
|
|
174
|
+
def self.float(i)
|
|
175
|
+
i[0].to_f
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
##
|
|
179
|
+
# Round instruction
|
|
180
|
+
# @param [Array] input value (val, decimal_places)
|
|
181
|
+
# @return [Integer|Float] output value
|
|
182
|
+
def self.round(i)
|
|
183
|
+
i[0].round(i[1])
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
##
|
|
187
|
+
# 'Switch' instruction (basically if)
|
|
188
|
+
#
|
|
189
|
+
# switch [condition (0/1/0.0/1.0), trueval, falseval]
|
|
190
|
+
# @param [Array] input value (condition, true val, false val)
|
|
191
|
+
# @return [Integer|Float] output value
|
|
192
|
+
def self.switch(i)
|
|
193
|
+
if i[0].to_i == 1
|
|
194
|
+
i[1]
|
|
195
|
+
else
|
|
196
|
+
i[2]
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
##
|
|
201
|
+
# Equal condition instruction
|
|
202
|
+
# @param [Array] input value (a, b)
|
|
203
|
+
# @return [Integer|Float] output value 1 true 0 falsew
|
|
204
|
+
def self.eq(i)
|
|
205
|
+
if i[0] == i[1]
|
|
206
|
+
1
|
|
207
|
+
else
|
|
208
|
+
0
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
##
|
|
213
|
+
# Logic invert instruction
|
|
214
|
+
# @param [Array] input value (0 or 1)
|
|
215
|
+
# @return [Integer|Float] output value (1 if in 0, 0 if in 1)
|
|
216
|
+
def self.bnot(i)
|
|
217
|
+
if i[0].to_i == 1
|
|
218
|
+
0
|
|
219
|
+
else
|
|
220
|
+
1
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Image Filter DSL Library
|
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
5
|
+
module ImageFilterDsl
|
|
6
|
+
module Dsl
|
|
7
|
+
module Kernel
|
|
8
|
+
##
|
|
9
|
+
# IFD Filter Kernel class
|
|
10
|
+
# @author vdtdev <vdtdev@gmail.com>
|
|
11
|
+
class FilterKernel
|
|
12
|
+
attr_accessor :instructions
|
|
13
|
+
attr_accessor :inputs
|
|
14
|
+
attr_accessor :outputs
|
|
15
|
+
|
|
16
|
+
##
|
|
17
|
+
# Kernel constructor
|
|
18
|
+
# @param [Array] inputs Input symbols
|
|
19
|
+
# @param [Array] outputs Output symbols
|
|
20
|
+
def initialize(inputs=[],outputs=[])
|
|
21
|
+
@inputs = inputs
|
|
22
|
+
@outputs = outputs
|
|
23
|
+
@instructions = []
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
##
|
|
27
|
+
# Store instruction in kernel
|
|
28
|
+
# @param [Kernel::KernelInstruction] instruction Instruction to add
|
|
29
|
+
# @return [Integer] total number of instructions
|
|
30
|
+
def store_instruction(instruction)
|
|
31
|
+
@instructions.append(instruction)
|
|
32
|
+
@instructions.length
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Process filter kernel and produce output hash
|
|
37
|
+
# @param [Hash] initial_values Hash of values for inputs
|
|
38
|
+
# @return [Hash] hash of result values for output symbols
|
|
39
|
+
def process(initial_values)
|
|
40
|
+
outs = Hash[* @outputs.map{|o| [o,nil] }.flatten]
|
|
41
|
+
@instructions.each do |i|
|
|
42
|
+
v = i.calculate(initial_values)
|
|
43
|
+
if @outputs.include?(i.out)
|
|
44
|
+
outs[i.out] = v
|
|
45
|
+
else
|
|
46
|
+
initial_values[i.out] = v
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
outs
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
##
|
|
54
|
+
# Class representing a FilterKernel instruction
|
|
55
|
+
class KernelInstruction
|
|
56
|
+
attr_accessor :op
|
|
57
|
+
attr_accessor :inputs
|
|
58
|
+
attr_accessor :out
|
|
59
|
+
|
|
60
|
+
##
|
|
61
|
+
# Construct new Filter Kernel Instruction
|
|
62
|
+
# @param [Symbol] op Instruction operation
|
|
63
|
+
# @param [Array] ins Input symbols
|
|
64
|
+
# @param [Symbol] out Output symbol
|
|
65
|
+
def initialize(op,ins,out)
|
|
66
|
+
@op = op
|
|
67
|
+
@inputs = ins
|
|
68
|
+
unless @inputs.kind_of?(Array)
|
|
69
|
+
@inputs = [@inputs]
|
|
70
|
+
end
|
|
71
|
+
@out = out
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Calculate instruction output
|
|
76
|
+
# @param [Hash] variables Hash of variable values
|
|
77
|
+
# @return [Integer|Float] output value
|
|
78
|
+
def calculate(variables)
|
|
79
|
+
vals = @inputs.map{|v| (v.kind_of?(Symbol))? variables[v] : v }
|
|
80
|
+
FilterInstructions.method(@op).call(vals)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
require 'chunky_png'
|
|
2
|
+
|
|
3
|
+
##
|
|
4
|
+
# Image Filter DSL Library
|
|
5
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
6
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
7
|
+
module ImageFilterDsl
|
|
8
|
+
module Engine
|
|
9
|
+
##
|
|
10
|
+
# Image Processor class applies Filter Kernels to images
|
|
11
|
+
class ImageProcessor
|
|
12
|
+
|
|
13
|
+
attr_accessor :kernel, :config
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# Constructor
|
|
17
|
+
# @param [Dsl::Kernel::FilterKernel|String] filter_kernel FilterKernel object or path to binary kernel file
|
|
18
|
+
# @param [Integer] thread_count How many threads for processor to use
|
|
19
|
+
def initialize(filter_kernel, thread_count=6)
|
|
20
|
+
@config = {
|
|
21
|
+
filter_kernel_filename: nil,
|
|
22
|
+
threads: thread_count
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if filter_kernel.is_a?(String)
|
|
26
|
+
@config[:filter_kernel_filename] = filter_kernel
|
|
27
|
+
|
|
28
|
+
@kernel = Engine::IO.read(
|
|
29
|
+
filter_kernel,
|
|
30
|
+
Engine::IO::DataFormat::KERNEL
|
|
31
|
+
)
|
|
32
|
+
else
|
|
33
|
+
@kernel = filter_kernel
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
##
|
|
38
|
+
# Process image
|
|
39
|
+
# @param [String] img_src_filename Source image filename
|
|
40
|
+
# @param [String] img_out_filename Output image filename
|
|
41
|
+
def process_image(img_src_filename, img_out_filename)
|
|
42
|
+
img = load_image(img_src_filename)
|
|
43
|
+
|
|
44
|
+
size = [img[:image].width, img[:image].height]
|
|
45
|
+
p_count = size[0] * size[1]
|
|
46
|
+
|
|
47
|
+
p_part = p_count / @config[:threads]
|
|
48
|
+
thread_loads = [].fill(p_part, (0..(@config[:threads])-1))
|
|
49
|
+
|
|
50
|
+
if (p_part * @config[:threads]) < p_count
|
|
51
|
+
thread_loads[-1] += (p_count - (p_part * @config[:threads]))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
ranges = thread_loads.map.with_index do |l,i|
|
|
55
|
+
start = thread_loads[0..i][0..-2].sum
|
|
56
|
+
fin = start + (l - 1)
|
|
57
|
+
(start..fin)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ranges = ranges.map{|r| r.map{|i| i }}
|
|
61
|
+
|
|
62
|
+
threads = []
|
|
63
|
+
|
|
64
|
+
(0..@config[:threads]-1).each do |i|
|
|
65
|
+
threads << Thread.new {
|
|
66
|
+
run_process(img, ranges[i])
|
|
67
|
+
}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
threads.each { |th| th.join }
|
|
71
|
+
|
|
72
|
+
save_image(img, img_out_filename)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
##
|
|
78
|
+
# Convert point index to x,y coord
|
|
79
|
+
# @param [Integer] p Point index
|
|
80
|
+
# @param [Integer] width Grid width
|
|
81
|
+
# @return [Hash] Coordinate {:x, :y}
|
|
82
|
+
def point_coord(p,width)
|
|
83
|
+
{
|
|
84
|
+
x: (p % width),
|
|
85
|
+
y: (p / width)
|
|
86
|
+
}
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Execute processing for point range
|
|
91
|
+
# Called inside Thread.new
|
|
92
|
+
# @param [Hash] data Image data
|
|
93
|
+
# @param [Array] range Array of range points
|
|
94
|
+
def run_process(data, range)
|
|
95
|
+
|
|
96
|
+
range.each do |p|
|
|
97
|
+
c = point_coord(p, data[:image].width)
|
|
98
|
+
process_pixel(data, c[:x], c[:y])
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Process pixel using filter
|
|
105
|
+
# @param [Hash] data Image data hash
|
|
106
|
+
# @param [Integer] x Source (possibly dest) X coordinate
|
|
107
|
+
# @param [Integer] y Source (possibly dest) Y coordinate
|
|
108
|
+
# @return [Hash] Copy of filter outputs
|
|
109
|
+
def process_pixel(data, x, y)
|
|
110
|
+
clr = ChunkyPNG::Color
|
|
111
|
+
pixel = data[:image].get_pixel(x,y)
|
|
112
|
+
|
|
113
|
+
# r = (clr.r(pixel)/255.0).round(2)
|
|
114
|
+
# g = (clr.g(pixel)/255.0).round(2)
|
|
115
|
+
# b = (clr.b(pixel)/255.0).round(2)
|
|
116
|
+
# a = (clr.a(pixel)/255.0).round(2)
|
|
117
|
+
|
|
118
|
+
r = clr.r(pixel)
|
|
119
|
+
g = clr.g(pixel)
|
|
120
|
+
b = clr.b(pixel)
|
|
121
|
+
a = clr.a(pixel)
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
inputs = Hash[*@kernel.inputs.map{|k|[k,0]}.flatten]
|
|
125
|
+
inputs[:x] = x if inputs.keys.include?(:x)
|
|
126
|
+
inputs[:y] = y if inputs.keys.include?(:y)
|
|
127
|
+
inputs[:r] = r if inputs.keys.include?(:r)
|
|
128
|
+
inputs[:g] = g if inputs.keys.include?(:g)
|
|
129
|
+
inputs[:b] = b if inputs.keys.include?(:b)
|
|
130
|
+
inputs[:a] = a if inputs.keys.include?(:a)
|
|
131
|
+
inputs[:width] = data[:image].width if inputs.keys.include?(:width)
|
|
132
|
+
inputs[:hght] = data[:image].height if inputs.keys.include?(:hght)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
o = @kernel.process(inputs)
|
|
136
|
+
|
|
137
|
+
outputs = {
|
|
138
|
+
x: x, y: y, r: r, g: g, b: b, a: a
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
o.keys.each do |k|
|
|
142
|
+
if o[k] == Float::INFINITY
|
|
143
|
+
p "Infinity: #{k}"
|
|
144
|
+
end
|
|
145
|
+
outputs[k] = o[k].to_i
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# o_color = clr.rgba(
|
|
149
|
+
# (255*outputs[:r]).to_i,
|
|
150
|
+
# (255*outputs[:g]).to_i,
|
|
151
|
+
# (255*outputs[:b]).to_i,
|
|
152
|
+
# (255*outputs[:a]).to_i
|
|
153
|
+
# )
|
|
154
|
+
|
|
155
|
+
o_color = clr.rgba(
|
|
156
|
+
outputs[:r].to_i,
|
|
157
|
+
outputs[:g].to_i,
|
|
158
|
+
outputs[:b].to_i,
|
|
159
|
+
outputs[:a].to_i
|
|
160
|
+
)
|
|
161
|
+
|
|
162
|
+
data[:image].set_pixel(
|
|
163
|
+
outputs[:x], outputs[:y],
|
|
164
|
+
o_color
|
|
165
|
+
)
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
##
|
|
169
|
+
# Load PNG image using ChunkyPNG
|
|
170
|
+
# @param [String] filename Filename of image to load
|
|
171
|
+
# @return [Hash] Hash with :stream as ChunkyPNG::Datastream and
|
|
172
|
+
# :image as ChunkyPNG::Image and :filename with original filename
|
|
173
|
+
def load_image(filename)
|
|
174
|
+
stream = ChunkyPNG::Datastream.from_file(filename)
|
|
175
|
+
image = ChunkyPNG::Image.from_datastream(stream)
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
filename: filename,
|
|
179
|
+
stream: stream,
|
|
180
|
+
image: image
|
|
181
|
+
}
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
##
|
|
185
|
+
# Save image data to disk as PNG
|
|
186
|
+
# @param [Hash] data Image data as provided by load_image
|
|
187
|
+
# @param [String] filename Optional filename to save to; if none given,
|
|
188
|
+
# uses the original filename from data hash
|
|
189
|
+
# @return [File] file object written to
|
|
190
|
+
def save_image(data, filename = nil)
|
|
191
|
+
# If no filename given, use original
|
|
192
|
+
filename = data[:filename] if filename.nil?
|
|
193
|
+
stream = data[:image].to_datastream()
|
|
194
|
+
stream.save(filename)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Image Filter DSL Library
|
|
3
|
+
# (c) 2018 VDTDEV/Wade H. ~ MIT License
|
|
4
|
+
# @author Wade H. <vdtdev@gmail.com>
|
|
5
|
+
module ImageFilterDsl
|
|
6
|
+
module Engine
|
|
7
|
+
##
|
|
8
|
+
# I/O methods for reading/writing Filter Kernel data to files
|
|
9
|
+
module IO
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Constants for format option used in read
|
|
13
|
+
module DataFormat
|
|
14
|
+
KERNEL = :kernel
|
|
15
|
+
RECORD = :record
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# Read Filter Kernel from file
|
|
20
|
+
# @param [String] filename Filename to read from
|
|
21
|
+
# @param [Symbol] format Format to return (constants from
|
|
22
|
+
# DataFormat module; KERNEL or RECORD)
|
|
23
|
+
# @return [Dsl::Kernel::FilterKernel|Binary::Struct::IfdKernel] loaded data,
|
|
24
|
+
# in specified format
|
|
25
|
+
def self.read(filename, format=DataFormat::KERNEL)
|
|
26
|
+
record = Binary::Struct::IfdKernel.new
|
|
27
|
+
File.open(filename, 'rb'){|f| record.read(f) }
|
|
28
|
+
|
|
29
|
+
if format == DataFormat::RECORD
|
|
30
|
+
return record
|
|
31
|
+
else
|
|
32
|
+
return Binary::Serialize.to_kernel(record)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
##
|
|
37
|
+
# Write filter kernel to file
|
|
38
|
+
# @param [String] filename File name to write to
|
|
39
|
+
# @param [Binary::Struct::IfdKernel|Dsl::Kernel::FilterKernel] filter Filter kernel or record to write
|
|
40
|
+
# @return [Binary::Struct::IfdKernel] IfdKernel record
|
|
41
|
+
def self.write(filename, filter)
|
|
42
|
+
rec = nil
|
|
43
|
+
if filter.is_a?(Dsl::Kernel::FilterKernel)
|
|
44
|
+
rec = Binary::Serialize.to_record(filter)
|
|
45
|
+
elsif filter.is_a?(Binary::Struct::IfdKernel)
|
|
46
|
+
rec = filter
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
File.open(filename, 'wb') { |f| rec.write(f) }
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: image_filter_dsl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.0.1
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Wade H.
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-07-02 00:00:00.000000000 Z
|
|
12
|
+
dependencies: []
|
|
13
|
+
description: Image Filter DSL with processing
|
|
14
|
+
email: vdtdev.prod@gmail.com
|
|
15
|
+
executables:
|
|
16
|
+
- image_filter_dsl
|
|
17
|
+
extensions: []
|
|
18
|
+
extra_rdoc_files: []
|
|
19
|
+
files:
|
|
20
|
+
- bin/image_filter_dsl
|
|
21
|
+
- lib/image_filter_dsl.rb
|
|
22
|
+
- lib/image_filter_dsl/binary/serialize.rb
|
|
23
|
+
- lib/image_filter_dsl/binary/struct.rb
|
|
24
|
+
- lib/image_filter_dsl/dsl/filter.rb
|
|
25
|
+
- lib/image_filter_dsl/dsl/filter_instructions.rb
|
|
26
|
+
- lib/image_filter_dsl/dsl/kernel.rb
|
|
27
|
+
- lib/image_filter_dsl/engine/image_processor.rb
|
|
28
|
+
- lib/image_filter_dsl/engine/io.rb
|
|
29
|
+
homepage: https://bitbucket.org/WadeH/image_filter_dsl/src
|
|
30
|
+
licenses:
|
|
31
|
+
- MIT
|
|
32
|
+
metadata:
|
|
33
|
+
source_code_uri: https://bitbucket.org/WadeH/image_filter_dsl/src
|
|
34
|
+
post_install_message:
|
|
35
|
+
rdoc_options: []
|
|
36
|
+
require_paths:
|
|
37
|
+
- lib
|
|
38
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
39
|
+
requirements:
|
|
40
|
+
- - ">="
|
|
41
|
+
- !ruby/object:Gem::Version
|
|
42
|
+
version: '0'
|
|
43
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
requirements: []
|
|
49
|
+
rubyforge_project:
|
|
50
|
+
rubygems_version: 2.7.8
|
|
51
|
+
signing_key:
|
|
52
|
+
specification_version: 4
|
|
53
|
+
summary: Image Filter DSL
|
|
54
|
+
test_files: []
|