gifenc 0.2.0 → 0.2.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 +4 -4
- data/CHANGELOG.md +7 -2
- data/README.md +2 -2
- data/lib/geometry.rb +40 -1
- data/lib/gif.rb +1 -1
- data/lib/image.rb +110 -39
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c38b840026c2289d2524c5e06bf2d1943b468662bd45a0f7abe456a6405badc
|
|
4
|
+
data.tar.gz: fc17037791eff68a2fec84b7689b082726e51aa1363b1e94885a9535f4b37db1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 54367e7f3751f0370fe6e4a297fb29b323a25d25ad9ef0e9a435ec8df3cc6010ff2f012d41cda0195cb8a42456069e15a03f3fdfd78f0990be1ecb78fd5f35f9
|
|
7
|
+
data.tar.gz: 12e4facd900f5628554bfc479044ffe33df18dfaf365586dbc259eeace2e643c9cecb6b187c7276f0eba64b04c1e4f987486635d0e4bae0d2572453d9b795962
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
### 0.2.1 (18/Mar/2024)
|
|
4
|
+
|
|
5
|
+
Improved the image copy method. It now automatically corrects both source and destination offsets / dimensions to prevent any out of bounds errors, allows to specify a bounding box to which the copying should be restricted to, and allows to "copy with transparency", which will copy every color except for the transparent one.
|
|
6
|
+
|
|
3
7
|
### 0.2.0 (05/Mar/2024)
|
|
4
8
|
|
|
5
9
|
A big update, with the main changes being divided in two categories: mathematical methods and drawing methods.
|
|
@@ -10,7 +14,7 @@ A big update, with the main changes being divided in two categories: mathematica
|
|
|
10
14
|
* Point operations, such as translations, dilations / scalings, linear and convex combinations, scalar product, center of mass, etc.
|
|
11
15
|
* Point transformations, such as rotations, projections and reflections.
|
|
12
16
|
* Norms (L1, euclidean, supremum), normalization, distances.
|
|
13
|
-
* Angles, parallelism,
|
|
17
|
+
* Angles, parallelism, orthogonality, normal vectors...
|
|
14
18
|
|
|
15
19
|
- Significantly expanded the drawing functionality, including:
|
|
16
20
|
* Improved line drawing, and added line styles (dashed, dotted, etc).
|
|
@@ -18,10 +22,11 @@ A big update, with the main changes being divided in two categories: mathematica
|
|
|
18
22
|
* Added grids, polygonal chains and spirals.
|
|
19
23
|
* Arbitrary parametric curves and function graphs given a lambda function.
|
|
20
24
|
* Implemented the flood fill / bucket tool.
|
|
25
|
+
* Added a general Brush class to enable more flexible drawing.
|
|
21
26
|
|
|
22
27
|
Many sample GIFs, showcasing all this new functionality, were added in the [Examples](https://github.com/edelkas/gifenc/tree/master/examples) folder as well.
|
|
23
28
|
|
|
24
|
-
-
|
|
29
|
+
- Additional new functionality includes:
|
|
25
30
|
* Copy: Ability to copy a region from one image to another.
|
|
26
31
|
* Compress: Ability to LZW-compress image data on the fly, instead of keeping
|
|
27
32
|
everything raw in memory until final encode time. Particularly helpful for
|
data/README.md
CHANGED
|
@@ -11,7 +11,7 @@ A pure Ruby library with no external dependencies that allows to encode, decode
|
|
|
11
11
|
* Have a decent suite of editing functionalities, so that the need for external tools is avoided as much as possible.
|
|
12
12
|
* Have a succint and comfortable syntax to use.
|
|
13
13
|
|
|
14
|
-
Currently, the specs are almost fully supported for encoding. Decoding is not yet available,
|
|
14
|
+
Currently, the specs are almost fully supported for encoding. Decoding is not yet available, but will be soon. There's a solid `Geometry` module and decent drawing functionality. See the [Reference](https://www.rubydoc.info/gems/gifenc) for the full documentation, as well as [Examples](https://github.com/edelkas/gifenc/tree/master/examples) for a list of sample snippets and GIFs.
|
|
15
15
|
|
|
16
16
|
## A first example
|
|
17
17
|
|
|
@@ -51,7 +51,7 @@ end
|
|
|
51
51
|
gif.save('test.gif')
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Let's see a step-by-step overview, refer to the
|
|
54
|
+
Let's see a step-by-step overview, refer to the documentation for an in-depth explanation of the actual details for each of the topics involved.
|
|
55
55
|
1. The first thing we do is create two **Color Tables**, one with red shades, and another with green shades. Since GIF is an indexed image format, it can only use colors from predefined palettes of at most 256 colors. `Gifenc` comes equipped with several default ones, but you can build your own, and operate with them.
|
|
56
56
|
2. We create the GIF object. We select the red color table to be the **GCT** (_Global Color Table_), which is used for all frames that do not contain an explicit **LCT** (_Local Color Table_). We also set the GIF to loop indefinitely.
|
|
57
57
|
3. We create the first frame, this will act as the background. We use the green color table as LCT for this frame. We set a few attributes, such as the default color of the canvas, the length of the frame, and the color used for transparency. We draw a sequence of centered green squares, they will help to see the transparency of the next frames.
|
data/lib/geometry.rb
CHANGED
|
@@ -542,6 +542,26 @@ module Gifenc
|
|
|
542
542
|
[x0, y0, x1 - x0 + 1, y1 - y0 + 1]
|
|
543
543
|
end
|
|
544
544
|
|
|
545
|
+
# Calculate the intersection of multiple rectangles. The rectangles must be
|
|
546
|
+
# supplied in the usual format of bounding boxes, i.e. `[X, Y, W, H]`,
|
|
547
|
+
# where `X` and `Y` are the coordinates of the upper left corner of the
|
|
548
|
+
# rectangle with respect to the image, and `W` and `H` are the width and
|
|
549
|
+
# height of the rectangle, respectively. The result will be a rectangle
|
|
550
|
+
# in the same format, if the intersection is non-empty, or `nil` otherwise.
|
|
551
|
+
# @param rects [Array<Array<Float>>] The rectangles to intersect.
|
|
552
|
+
# @return [Array<Float>,nil] The resulting intersection.
|
|
553
|
+
def self.rect_overlap(*rects)
|
|
554
|
+
return if rects.empty?
|
|
555
|
+
|
|
556
|
+
rect = rects[0]
|
|
557
|
+
rects[1..-1].each{ |r|
|
|
558
|
+
rect = rect_overlap_two(rect, r)
|
|
559
|
+
return if !rect
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
rect
|
|
563
|
+
end
|
|
564
|
+
|
|
545
565
|
# Translate a set of points according to a fixed vector. Given a list of
|
|
546
566
|
# points `P1, ... , Pn` and a translation vector `t`, this method will
|
|
547
567
|
# return the transformed list of points `P1 + t, ... , Pn + t`.
|
|
@@ -673,7 +693,8 @@ module Gifenc
|
|
|
673
693
|
# Find the center of mass (barycenter) of a list of points. This will always
|
|
674
694
|
# be contained within the convex hull of the supplied points.
|
|
675
695
|
# @param points [Array<Point>] The list of points.
|
|
676
|
-
# @return [Point]
|
|
696
|
+
# @return [Point] The center of mass of the points.
|
|
697
|
+
# @raise [Exception::GeometryError] If the list of points is empty.
|
|
677
698
|
def self.center(points)
|
|
678
699
|
raise Exception::GeometryError, "Cannot find center of empty list of points." if points.size == 0
|
|
679
700
|
points.map!{ |p| Point.parse(p) }
|
|
@@ -690,5 +711,23 @@ module Gifenc
|
|
|
690
711
|
(r.y - p.y) * (q.x - p.x) - (q.y - p.y) * (r.x - p.x)
|
|
691
712
|
end
|
|
692
713
|
|
|
714
|
+
# Find overlap of 2 rectangles. Used recursively by rect_overlap to find
|
|
715
|
+
# overlap of N rectangles.
|
|
716
|
+
def self.rect_overlap_two(rect1, rect2)
|
|
717
|
+
return nil if !rect1 || !rect2
|
|
718
|
+
|
|
719
|
+
x_max = [rect1[0], rect2[0]].max
|
|
720
|
+
y_max = [rect1[1], rect2[1]].max
|
|
721
|
+
x_min = [rect1[0] + rect1[2], rect2[0] + rect2[2]].min
|
|
722
|
+
y_min = [rect1[1] + rect1[3], rect2[1] + rect2[3]].min
|
|
723
|
+
|
|
724
|
+
x = x_max
|
|
725
|
+
y = y_max
|
|
726
|
+
w = x_min - x_max
|
|
727
|
+
h = y_min - y_max
|
|
728
|
+
|
|
729
|
+
[w, h].min > PRECISION ? [x, y, w, h] : nil
|
|
730
|
+
end
|
|
731
|
+
|
|
693
732
|
end # Module Geometry
|
|
694
733
|
end # Module Gifenc
|
data/lib/gif.rb
CHANGED
data/lib/image.rb
CHANGED
|
@@ -186,6 +186,7 @@ module Gifenc
|
|
|
186
186
|
# @return (see #delay)
|
|
187
187
|
# @see (see #delay)
|
|
188
188
|
def delay=(value)
|
|
189
|
+
return if !value
|
|
189
190
|
@gce = Extension::GraphicControl.new if !@gce
|
|
190
191
|
@gce.delay = value
|
|
191
192
|
end
|
|
@@ -207,6 +208,7 @@ module Gifenc
|
|
|
207
208
|
# @return (see #disposal)
|
|
208
209
|
# @see (see #disposal)
|
|
209
210
|
def disposal=(value)
|
|
211
|
+
return if !value
|
|
210
212
|
@gce = Extension::GraphicControl.new if !@gce
|
|
211
213
|
@gce.disposal = value
|
|
212
214
|
end
|
|
@@ -226,6 +228,7 @@ module Gifenc
|
|
|
226
228
|
# @return (see #trans_color)
|
|
227
229
|
# @see (see #trans_color)
|
|
228
230
|
def trans_color=(value)
|
|
231
|
+
return if !value
|
|
229
232
|
@gce = Extension::GraphicControl.new if !@gce
|
|
230
233
|
@gce.trans_color = value
|
|
231
234
|
end
|
|
@@ -268,49 +271,105 @@ module Gifenc
|
|
|
268
271
|
self
|
|
269
272
|
end
|
|
270
273
|
|
|
271
|
-
#
|
|
274
|
+
# Destroy the pixel data of the image. This simply substitutes the contents
|
|
272
275
|
# of the array, hoping that the underlying data will go out of scope and
|
|
273
276
|
# be collected by the garbage collector. This is intended for freeing
|
|
274
277
|
# space and to simulate "destroying" the image.
|
|
275
278
|
# @return (see #initialize)
|
|
276
|
-
def
|
|
279
|
+
def destroy
|
|
277
280
|
@pixels = nil
|
|
278
281
|
self
|
|
279
282
|
end
|
|
280
283
|
|
|
281
|
-
#
|
|
282
|
-
#
|
|
284
|
+
# Paint the whole canvas with the base image color.
|
|
285
|
+
# @return (see #initialize)
|
|
286
|
+
def clear
|
|
287
|
+
@pixels = [@color] * (@width * @height)
|
|
288
|
+
self
|
|
289
|
+
end
|
|
290
|
+
|
|
291
|
+
# Copy a rectangular region from another image to this one. The dimension of
|
|
292
|
+
# the region, as well as the source offset and the destination offset, can
|
|
293
|
+
# be provided. If the region would go out of bounds, the function will
|
|
294
|
+
# just gracefully crop it rather than failing. Additionally, a more restrictive
|
|
295
|
+
# bounding box (smaller than the full image) can also be provided, to where
|
|
296
|
+
# the copying will be confined.
|
|
283
297
|
# @note The two images are assumed to have the same color table, since what
|
|
284
298
|
# is copied is the color indexes.
|
|
285
|
-
# @param
|
|
299
|
+
# @param src [Image] The source image to copy the contents from.
|
|
286
300
|
# @param offset [Array<Integer>] The coordinates of the offset of the region
|
|
287
301
|
# in the source image.
|
|
288
302
|
# @param dim [Array<Integer>] The dimensions of the region, in the form `[W, H]`,
|
|
289
|
-
# where W is the width and H is the height of the rectangle to copy.
|
|
303
|
+
# where W is the width and H is the height of the rectangle to copy. If
|
|
304
|
+
# unspecified (`nil`), the whole source image will be copied.
|
|
290
305
|
# @param dest [Array<Integer>] The coordinates of the destination offset of
|
|
291
306
|
# the region in this image.
|
|
292
|
-
# @
|
|
293
|
-
# the
|
|
307
|
+
# @param trans [Boolean] If enabled, the pixels from the source image whose
|
|
308
|
+
# color is the transparent one (for the source image) won't be copied over,
|
|
309
|
+
# effectively achieving the usual GIF composition with basic transparency.
|
|
310
|
+
# It's a bit slower as a consequence.
|
|
311
|
+
# @param bbox [Array<Integer>] The bounding box (with respect to this image)
|
|
312
|
+
# where the copying should be restricted to. Everything outside this region
|
|
313
|
+
# will be left untouched. If unspecified (`nil`), this will be the whole
|
|
314
|
+
# image. As usual, the format is `[X, Y, W, H]`, where `[X, Y]` are the
|
|
315
|
+
# coordinates of the upper left pixel, and `[W, H]` are the pixel width
|
|
316
|
+
# and height, respectively.
|
|
317
|
+
# @raise [Exception::CanvasError] If no source provided.
|
|
294
318
|
# @return (see #initialize)
|
|
295
|
-
def copy(
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
319
|
+
def copy(src: nil, offset: [0, 0], dim: nil, dest: [0, 0], trans: false, bbox: nil)
|
|
320
|
+
raise Exception::CanvasError, "Cannot copy, no source provided." if !src
|
|
321
|
+
|
|
322
|
+
# Parse parameters
|
|
323
|
+
bbox = [0, 0, @width, @height] unless bbox
|
|
324
|
+
dim = [src.width, src.height] unless dim
|
|
325
|
+
offset = Geometry::Point.parse(offset).round
|
|
326
|
+
dim = Geometry::Point.parse(dim).round
|
|
327
|
+
dest = Geometry::Point.parse(dest).round
|
|
328
|
+
|
|
329
|
+
# Normalize main bbox
|
|
330
|
+
bbox = Geometry.rect_overlap(bbox, [0, 0, @width, @height])
|
|
331
|
+
return if !bbox
|
|
332
|
+
bbox.map!(&:round)
|
|
333
|
+
|
|
334
|
+
# Normalize source bbox
|
|
335
|
+
src_bbox = [offset.x, offset.y, dim.x, dim.y]
|
|
336
|
+
src_bbox = Geometry.rect_overlap(src_bbox, [0, 0, src.width, src.height])
|
|
337
|
+
return if !src_bbox
|
|
338
|
+
offset = Geometry::Point.parse(src_bbox[0, 2])
|
|
339
|
+
dim = Geometry::Point.parse(src_bbox[2, 2])
|
|
340
|
+
|
|
341
|
+
# Normalize destination bbox
|
|
342
|
+
dest_bbox = [dest.x, dest.y, dim.x, dim.y]
|
|
343
|
+
overlap = Geometry.rect_overlap(dest_bbox, bbox)
|
|
344
|
+
return if !dest_bbox
|
|
345
|
+
dest = Geometry::Point.parse(overlap[0, 2])
|
|
346
|
+
dim = Geometry::Point.parse(overlap[2, 2])
|
|
347
|
+
|
|
348
|
+
# Transform coordinates of source to coordinates of destination
|
|
349
|
+
offset += Gifenc::Geometry.transform([dest], dest_bbox)[0]
|
|
350
|
+
|
|
351
|
+
# Handy fetch
|
|
352
|
+
dx, dy = dest.x.round, dest.y.round
|
|
353
|
+
ox, oy = offset.x.round, offset.y.round
|
|
354
|
+
lx, ly = dim.x.round, dim.y.round
|
|
355
|
+
bg = src.trans_color
|
|
356
|
+
|
|
357
|
+
# Copy pixel data. We use a different, slightly faster, algorithm if we
|
|
358
|
+
# don't have a bg color check to make, by directly copying full rows.
|
|
359
|
+
if trans && bg
|
|
360
|
+
c = nil
|
|
361
|
+
ly.times.each{ |y|
|
|
362
|
+
lx.times.each{ |x|
|
|
363
|
+
c = src[ox + x, oy + y]
|
|
364
|
+
self[dx + x, dy + y] = c unless c == bg
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
else
|
|
368
|
+
ly.times.each{ |y|
|
|
369
|
+
@pixels[(dy + y) * @width + dx, lx] = src.pixels[(oy + y) * src.width + ox, lx]
|
|
370
|
+
}
|
|
304
371
|
end
|
|
305
372
|
|
|
306
|
-
dx = dest.x.round
|
|
307
|
-
dy = dest.y.round
|
|
308
|
-
ox = offset.x.round
|
|
309
|
-
oy = offset.y.round
|
|
310
|
-
dim.y.round.times.each{ |y|
|
|
311
|
-
@pixels[(dy + y) * @width + dx, dim.x.round] = source.pixels[(oy + y) * source.width + ox, dim.x.round]
|
|
312
|
-
}
|
|
313
|
-
|
|
314
373
|
self
|
|
315
374
|
end
|
|
316
375
|
|
|
@@ -459,9 +518,11 @@ module Gifenc
|
|
|
459
518
|
# indicates the position of the line with respect to the coordinates. It
|
|
460
519
|
# must be in the interval [-1, 1]. A value of `0` centers the line in its
|
|
461
520
|
# width, a value of `-1` draws it on one side, and `1` on the other.
|
|
462
|
-
# @param bbox [Array<Integer>] Bounding box determining the region
|
|
463
|
-
# the
|
|
464
|
-
#
|
|
521
|
+
# @param bbox [Array<Integer>] Bounding box determining the region to which
|
|
522
|
+
# the drawing will be restricted, in the format `[X, Y, W, H]`, where `[X, Y]`
|
|
523
|
+
# are the coordinates of the upper left corner of the box, and `[W, H]` are
|
|
524
|
+
# the pixel dimensions. If unspecified (`nil`), this defaults to the whole
|
|
525
|
+
# image.
|
|
465
526
|
# @param avoid [Array<Integer>] List of colors over which the line should
|
|
466
527
|
# NOT be drawn.
|
|
467
528
|
# @param style [Symbol] Named style / pattern of the line. Can be `:solid`
|
|
@@ -530,19 +591,29 @@ module Gifenc
|
|
|
530
591
|
# * For `0` the border is centered around the boundary.
|
|
531
592
|
# * For `1` the border is entirely contained within the boundary.
|
|
532
593
|
# * For `-1` the border is entirely surrounding the boundary.
|
|
594
|
+
# @param style [Symbol] Border line style (`:solid`, `:dashed`, `:dotted`).
|
|
595
|
+
# See `style` param in {#line}).
|
|
596
|
+
# @param density [Symbol] Border line pattern density (`:normal`, `:dense`,
|
|
597
|
+
# `:loose`). See `density` param in {#line}.
|
|
598
|
+
# @param bbox [Array<Integer>] Bounding box determining the region to which
|
|
599
|
+
# the drawing will be restricted. See `bbox` param in {#line}.
|
|
533
600
|
# @return (see #initialize)
|
|
534
601
|
# @raise [Exception::CanvasError] If the rectangle would go out of bounds.
|
|
535
602
|
def rect(x, y, w, h, stroke = nil, fill = nil, weight: 1, anchor: 1,
|
|
536
|
-
style: :solid, density: :normal)
|
|
537
|
-
#
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
603
|
+
style: :solid, density: :normal, bbox: nil)
|
|
604
|
+
# Normalize bbox
|
|
605
|
+
bbox = [0, 0, @width, @height] unless bbox
|
|
606
|
+
bbox = Geometry.rect_overlap(bbox, [0, 0, @width, @height])
|
|
607
|
+
return if !bbox
|
|
608
|
+
bbox.map!(&:round)
|
|
609
|
+
|
|
610
|
+
# Intersect bbox with rectangle
|
|
611
|
+
rect_bbox = Geometry.rect_overlap(bbox, [x, y, w, h])
|
|
612
|
+
return if !rect_bbox
|
|
613
|
+
x0 = rect_bbox[0].round
|
|
614
|
+
y0 = rect_bbox[1].round
|
|
615
|
+
x1 = (rect_bbox[0] + rect_bbox[2]).round - 1
|
|
616
|
+
y1 = (rect_bbox[1] + rect_bbox[3]).round - 1
|
|
546
617
|
|
|
547
618
|
# Fill rectangle, if provided
|
|
548
619
|
if fill
|
|
@@ -559,7 +630,7 @@ module Gifenc
|
|
|
559
630
|
o = ((weight - 1) / 2.0 * anchor).round
|
|
560
631
|
w -= 2 * o - (weight % 2 == 0 ? 1 : 0)
|
|
561
632
|
h -= 2 * o - (weight % 2 == 0 ? 1 : 0)
|
|
562
|
-
rect(
|
|
633
|
+
rect(x0 + o, y0 + o, w, h, stroke, weight: weight, anchor: 0)
|
|
563
634
|
else
|
|
564
635
|
points = [[x0, y0], [x1, y0], [x1, y1], [x0, y1]]
|
|
565
636
|
4.times.each{ |i|
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gifenc
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- edelkas
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2024-03-
|
|
11
|
+
date: 2024-03-18 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: lzwrb
|
|
@@ -27,12 +27,12 @@ dependencies:
|
|
|
27
27
|
description: |2
|
|
28
28
|
This library provides GIF encoding, decoding and editing capabilities natively
|
|
29
29
|
within Ruby. It aims to support the complete GIF specification for both
|
|
30
|
-
encoding and decoding, as well as having
|
|
30
|
+
encoding and decoding, as well as having decent editing functionality, while
|
|
31
31
|
maintaining a succint syntax.
|
|
32
32
|
|
|
33
|
-
The current version
|
|
34
|
-
|
|
35
|
-
|
|
33
|
+
The current version only supports encoding, together with a decent drawing suite
|
|
34
|
+
The gem is actively developed and decoding will soon follow, so stay tuned
|
|
35
|
+
if you're interested!
|
|
36
36
|
email:
|
|
37
37
|
executables: []
|
|
38
38
|
extensions: []
|