From 37a466f7089b88f5554eafbe9b4bcee7290868c7 Mon Sep 17 00:00:00 2001 From: David Thompson Date: Wed, 7 Apr 2021 17:04:39 -0400 Subject: graphics: Rewrite 9-patch implementation and move it to its own module. --- Makefile.am | 1 + chickadee/graphics/9-patch.scm | 235 +++++++++++++++++++++++++++++++++++++++++ chickadee/graphics/sprite.scm | 130 ----------------------- doc/api.texi | 54 +++++----- examples/9-patch.scm | 17 +++ examples/nine-patch.scm | 17 --- 6 files changed, 282 insertions(+), 172 deletions(-) create mode 100644 chickadee/graphics/9-patch.scm create mode 100644 examples/9-patch.scm delete mode 100644 examples/nine-patch.scm diff --git a/Makefile.am b/Makefile.am index 6dd8dfc..d397f3a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -75,6 +75,7 @@ SOURCES = \ chickadee/graphics/viewport.scm \ chickadee/graphics/framebuffer.scm \ chickadee/graphics/sprite.scm \ + chickadee/graphics/9-patch.scm \ chickadee/graphics/font.scm \ chickadee/graphics/tiled.scm \ chickadee/graphics/particles.scm \ diff --git a/chickadee/graphics/9-patch.scm b/chickadee/graphics/9-patch.scm new file mode 100644 index 0000000..833fa42 --- /dev/null +++ b/chickadee/graphics/9-patch.scm @@ -0,0 +1,235 @@ +;;; Chickadee Game Toolkit +;;; Copyright © 2021 David Thompson +;;; +;;; Chickadee is free software: you can redistribute it and/or modify +;;; it under the terms of the GNU General Public License as published +;;; by the Free Software Foundation, either version 3 of the License, +;;; or (at your option) any later version. +;;; +;;; Chickadee is distributed in the hope that it will be useful, but +;;; WITHOUT ANY WARRANTY; without even the implied warranty of +;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +;;; General Public License for more details. +;;; +;;; You should have received a copy of the GNU General Public License +;;; along with this program. If not, see +;;; . + +(define-module (chickadee graphics 9-patch) + #:use-module (ice-9 match) + #:use-module (chickadee math matrix) + #:use-module (chickadee math rect) + #:use-module (chickadee math vector) + #:use-module (chickadee graphics blend) + #:use-module (chickadee graphics color) + #:use-module (chickadee graphics engine) + #:use-module (chickadee graphics shader) + #:use-module (chickadee graphics texture) + #:use-module (chickadee graphics buffer) + #:export (draw-9-patch* + draw-9-patch)) + +(define-geometry-type <9-patch-vertex> + 9-patch-vertex-ref + 9-patch-vertex-set! + 9-patch-vertex-append! + (position vec2) + (distance vec2)) + +(define-graphics-variable 9-patch-geometry + (make-geometry <9-patch-vertex> 4 #:index-capacity 6)) +(define-graphics-variable 9-patch-model-matrix (make-null-matrix4)) +(define-graphics-variable 9-patch-mvp-matrix (make-null-matrix4)) +(define-graphics-variable 9-patch-margins (make-null-rect)) +(define-graphics-variable 9-patch-shader + (strings->shader + " +#ifdef GLSL330 +layout (location = 0) in vec2 position; +layout (location = 1) in vec2 distance; +#elif defined(GLSL130) +in vec2 position; +in vec2 distance; +#elif defined(GLSL120) +attribute vec2 position; +attribute vec2 distance; +#endif +#ifdef GLSL120 +varying vec2 fragDistance; +#else +out vec2 fragDistance; +#endif +uniform mat4 mvp; + +void main(void) { + fragDistance = distance; + gl_Position = mvp * vec4(position.xy, 0.0, 1.0); +} +" + " +#ifdef GLSL120 +varying vec2 fragDistance; +#else +in vec2 fragDistance; +#endif +#ifdef GLSL330 +out vec4 fragColor; +#endif +uniform vec4 subtexture; +uniform vec4 margins; +uniform float width; +uniform float height; +uniform sampler2D colorTexture; +uniform vec4 tint; +uniform int mode; + +float patch(float d, float m0, float m1, float length, float texLength) { + if(d <= m0) { // inside the left/bottom margin. + return d; + } else if(d >= length - m1) { // inside the right/top margin. + return texLength - (length - d); + } else if(mode == 0) { // in the middle, stretch mode. + return mix(m0, texLength - m1, (d - m0) / (length - m0 - m1)); + } else { // in the middle, tile mode. + return m0 + mod((d - m0), texLength - m0 - m1); + } +} + +void main (void) { + vec2 texcoord = subtexture.xy; + texcoord.x += patch(fragDistance.x, margins.x, margins.y, width, subtexture.z); + texcoord.y += patch(fragDistance.y, margins.z, margins.w, height, subtexture.w); + +#ifdef GLSL330 + fragColor = texture(colorTexture, texcoord) * tint; +#else + gl_FragColor = texture2D(colorTexture, texcoord) * tint; +#endif +} +")) + +(define* (draw-9-patch* texture + rect + matrix + #:key + (margin 0.0) + (top-margin margin) + (bottom-margin margin) + (left-margin margin) + (right-margin margin) + (mode 'stretch) + (tint white) + (blend-mode blend:alpha) + (texcoords (texture-gl-tex-rect texture))) + (let ((shader (graphics-variable-ref 9-patch-shader)) + (geometry (graphics-variable-ref 9-patch-geometry)) + (mvp (graphics-variable-ref 9-patch-mvp-matrix)) + (margins (graphics-variable-ref 9-patch-margins))) + (let* ((w (rect-width rect)) + (h (rect-height rect)) + (tex-rect (texture-gl-rect texture)) + (tw (rect-width tex-rect)) + (th (rect-height tex-rect)) + ;; Convert pixel coordinates to GL texture coordinates. + (w* (/ w tw)) + (h* (/ h th))) + (with-geometry geometry + (let* ((x1 (rect-x rect)) + (y1 (rect-y rect)) + (x2 (+ x1 w)) + (y2 (+ y1 h)) + (s1 0.0) + (t1 0.0) + (s2 w*) + (t2 h*)) + (9-patch-vertex-append! geometry + (x1 y1 s1 t1) + (x2 y1 s2 t1) + (x2 y2 s2 t2) + (x1 y2 s1 t2)) + (geometry-index-append! geometry 0 3 2 0 2 1))) + ;; Convert pixel margin values to GL texture values. + (set-rect-x! margins (/ left-margin tw)) + (set-rect-y! margins (/ right-margin tw)) + (set-rect-width! margins (/ bottom-margin th)) + (set-rect-height! margins (/ top-margin th)) + (with-graphics-state ((g:blend-mode blend-mode) + (g:texture-0 texture)) + (shader-apply shader + (geometry-vertex-array geometry) + #:width w* + #:height h* + #:subtexture texcoords + #:margins margins + #:mode (match mode + ('stretch 0) + ('tile 1)) + #:tint tint + #:mvp (if matrix + (begin + (matrix4-mult! mvp matrix + (current-projection)) + mvp) + (current-projection))))))) + +(define %null-vec2 (vec2 0.0 0.0)) +(define %default-scale (vec2 1.0 1.0)) + +(define draw-9-patch + (let ((position (vec2 0.0 0.0)) + (%rect (make-rect 0.0 0.0 0.0 0.0)) + (matrix (make-null-matrix4))) + (lambda* (texture + rect + #:key + (margin 0.0) + (top-margin margin) + (bottom-margin margin) + (left-margin margin) + (right-margin margin) + (mode 'stretch) + (origin %null-vec2) + (rotation 0.0) + (scale %default-scale) + (blend-mode blend:alpha) + (tint white)) + "Draw a 9-patch over the area RECT using TEXTURE whose +stretchable/tileable patches are defined by the given margin +measurements. The corners are never stretched/tiled, the left and +right edges will be stretched/tiled vertically, the top and bottom +edges may be stretched/tiled horizontally, and the center may be +stretched/tiled in both directions. + +MODE may be either 'stretch' (the default) or 'tile'. + +MARGIN specifies the margin size for all sides of the 9-patch. To +make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN, +LEFT-MARGIN, and RIGHT-MARGIN arguments may be used. + +ORIGIN, ROTATION, and SCALE allow for arbitrary transformation of the +9-patch. + +BLEND-MODE specifies the blending mode to use. Alpha blending is used +by default. + +TINT specifies the color to tint the texture. White is used by +default, which means there is no tint." + (set-rect-x! %rect 0.0) + (set-rect-y! %rect 0.0) + (set-rect-width! %rect (rect-width rect)) + (set-rect-height! %rect (rect-height rect)) + (set-vec2-x! position (rect-x rect)) + (set-vec2-y! position (rect-y rect)) + (matrix4-2d-transform! matrix + #:origin origin + #:position position + #:rotation rotation + #:scale scale) + (draw-9-patch* texture %rect matrix + #:top-margin top-margin + #:bottom-margin bottom-margin + #:left-margin left-margin + #:right-margin right-margin + #:mode mode + #:blend-mode blend-mode + #:tint tint)))) diff --git a/chickadee/graphics/sprite.scm b/chickadee/graphics/sprite.scm index 0d6f838..8ab12f9 100644 --- a/chickadee/graphics/sprite.scm +++ b/chickadee/graphics/sprite.scm @@ -365,133 +365,3 @@ may be specified via the TEXTURE-REGION argument." #:rotation rotation #:scale scale) (draw-sprite-batch* batch matrix #:blend-mode blend-mode))) - - -;;; -;;; Nine Patches -;;; - -(define draw-nine-patch* - (let ((%rect (make-rect 0.0 0.0 0.0 0.0)) - (texcoords (make-rect 0.0 0.0 0.0 0.0))) - (lambda* (texture - rect - matrix - #:key - (margin 0.0) - (top-margin margin) - (bottom-margin margin) - (left-margin margin) - (right-margin margin) - (blend-mode blend:alpha) - (tint white)) - (let* ((x (rect-x rect)) - (y (rect-y rect)) - (w (rect-width rect)) - (h (rect-height rect)) - (border-x1 x) - (border-y1 y) - (border-x2 (+ x w)) - (border-y2 (+ y h)) - (fill-x1 (+ border-x1 left-margin)) - (fill-y1 (+ border-y1 bottom-margin)) - (fill-x2 (- border-x2 right-margin)) - (fill-y2 (- border-y2 top-margin)) - (prect (texture-gl-rect texture)) - (trect (texture-gl-tex-rect texture)) - (tw (rect-width prect)) - (th (rect-height prect)) - (border-s1 (rect-x trect)) - (border-t1 (rect-y trect)) - (border-s2 (+ (rect-x trect) (rect-width trect))) - (border-t2 (+ (rect-y trect) (rect-height trect))) - (fill-s1 (+ border-s1 (/ left-margin tw))) - (fill-t1 (+ border-t1 (/ top-margin th))) - (fill-s2 (- border-s2 (/ right-margin tw))) - (fill-t2 (- border-t2 (/ bottom-margin th)))) - (define (draw-piece x1 y1 x2 y2 s1 t1 s2 t2) - (set-rect-x! %rect x1) - (set-rect-y! %rect y1) - (set-rect-width! %rect (- x2 x1)) - (set-rect-height! %rect (- y2 y1)) - (set-rect-x! texcoords s1) - (set-rect-y! texcoords t1) - (set-rect-width! texcoords (- s2 s1)) - (set-rect-height! texcoords (- t2 t1)) - (draw-sprite* texture %rect matrix - #:texcoords texcoords - #:blend-mode blend-mode - #:tint tint)) - ;; bottom-left - (draw-piece border-x1 border-y1 fill-x1 fill-y1 - border-s1 fill-t2 fill-s1 border-t2) - ;; bottom-center - (draw-piece fill-x1 border-y1 fill-x2 fill-y1 - fill-s1 fill-t2 fill-s2 border-t2) - ;; bottom-right - (draw-piece fill-x2 border-y1 border-x2 fill-y1 - fill-s2 fill-t2 border-s2 border-t2) - ;; center-left - (draw-piece border-x1 fill-y1 fill-x1 fill-y2 - border-s1 fill-t2 fill-s1 fill-t1) - ;; center - (draw-piece fill-x1 fill-y1 fill-x2 fill-y2 - fill-s1 fill-t2 fill-s2 fill-t1) - ;; center-right - (draw-piece fill-x2 fill-y1 border-x2 fill-y2 - fill-s2 fill-t2 border-s2 fill-t1) - ;; top-left - (draw-piece border-x1 fill-y2 fill-x1 border-y2 - border-s1 border-t1 fill-s1 fill-t1) - ;; top-center - (draw-piece fill-x1 fill-y2 fill-x2 border-y2 - fill-s1 border-t1 fill-s2 fill-t1) - ;; top-right - (draw-piece fill-x2 fill-y2 border-x2 border-y2 - fill-s2 border-t1 border-s2 fill-t1))))) - -(define draw-nine-patch - (let ((position (vec2 0.0 0.0)) - (%rect (make-rect 0.0 0.0 0.0 0.0)) - (matrix (make-null-matrix4))) - (lambda* (texture - rect - #:key - (margin 0.0) - (top-margin margin) (bottom-margin margin) - (left-margin margin) (right-margin margin) - (origin %null-vec2) - (rotation 0.0) - (scale %default-scale) - (blend-mode blend:alpha) - (tint white)) - "Draw a \"nine patch\" sprite. A nine patch sprite renders -TEXTURE on the rectangular area RECT whose stretchable areas are -defined by the given margin measurements. The corners are never -stretched, the left and right edges may be stretched vertically, the -top and bottom edges may be stretched horizontally, and the center may -be stretched in both directions. This rendering technique is -particularly well suited for resizable windows and buttons in -graphical user interfaces. - -MARGIN specifies the margin size for all sides of the nine patch. To -make margins of differing sizes, the TOP-MARGIN, BOTTOM-MARGIN, -LEFT-MARGIN, and RIGHT-MARGIN arguments may be used." - (set-rect-x! %rect 0.0) - (set-rect-y! %rect 0.0) - (set-rect-width! %rect (rect-width rect)) - (set-rect-height! %rect (rect-height rect)) - (set-vec2-x! position (rect-x rect)) - (set-vec2-y! position (rect-y rect)) - (matrix4-2d-transform! matrix - #:origin origin - #:position position - #:rotation rotation - #:scale scale) - (draw-nine-patch* texture %rect matrix - #:top-margin top-margin - #:bottom-margin bottom-margin - #:left-margin left-margin - #:right-margin right-margin - #:blend-mode blend-mode - #:tint tint)))) diff --git a/doc/api.texi b/doc/api.texi index 5fa18b3..e99c89c 100644 --- a/doc/api.texi +++ b/doc/api.texi @@ -1586,6 +1586,7 @@ blocks to implement additional rendering techniques. * Colors:: Such pretty colors... * Textures:: 2D images. * Sprites:: Draw 2D images. +* 9-Patches:: Scalable bitmap boxes. * Fonts:: Drawing text. * Vector Paths:: Draw filled and stroked paths. * Particles:: Pretty little flying pieces! @@ -2041,48 +2042,51 @@ Render @var{batch} using @var{blend-mode}. Alpha blending is used by default. @end deffn -With a basic sprite abstraction in place, it's possible to build other -abstractions on top of it. One such example is the ``nine patch''. A -nine patch is a sprite that can be rendered at various sizes without -becoming distorted. This is achieved by dividing up the sprite into -nine regions: +@node 9-Patches +@subsection 9-Patches + +A 9-patch is a method of rendering a texture so that it can be +stretched to cover an area of any size without becoming distorted. +This is achieved by dividing up the sprite into nine regions: @itemize @item -the center, which can be scaled horizontally and vertically +the center, which can be stretched or tiled horizontally and vertically @item -the four corners, which can never be scaled +the four corners, which are never stretched or tiled @item -the left and right sides, which can be scaled vertically +the left and right sides, which can be stretched or tiled vertically @item -the top and bottom sides, which can be scaled horizontally +the top and bottom sides, which can be stretched or tiled horizontally @end itemize -The one caveat is that the bitmap regions must be designed in such a -way so that they are not distorted when stretched along the affected -axes. For example, that means that the top and bottom sides could -have varying colored pixels vertically, but not horizontally. - The most common application of this technique is for graphical user -interface widgets like buttons and dialog boxes. By using a nine -patch, they can be rendered at any size without unappealing scaling -artifacts. +interface widgets like buttons and dialog boxes which are often +dynamically resizable. By using a 9-patch, they can be rendered at +any size without scaling artifacts. @deffn {Procedure} draw-nine-patch texture rect @ [#:margin 0] [#:top-margin margin] [#:bottom-margin margin] @ - [#:left-margin margin] [#:right-margin margin] @ + [#:left-margin margin] [#:right-margin margin] [#:mode stretch] @ [#:origin] [#:scale] [#:rotation] [#:blend-mode] @ [#:tint white] -Draw a nine patch sprite. A nine patch sprite renders @var{texture} -as a @var{width} x @var{height} rectangle whose stretchable areas are -defined by the given margin measurements @var{top-margin}, -@var{bottom-margin}, @var{left-margin}, and @var{right-margin}. The -@var{margin} argument may be used to configure all four margins at -once. +Draw a 9-patch over the area @var{rect} using @var{texture} whose +stretchable/tileable patches are defined by the given margin +measurements. The corners are never stretched/tiled, the left and +right edges will be stretched/tiled vertically, the top and bottom +edges may be stretched/tiled horizontally, and the center may be +stretched/tiled in both directions. + +@var{mode} may be either @code{stretch} (the default) or @code{tile}. + +@var{margin} specifies the margin size for all sides of the 9-patch. +To make margins of differing sizes, the @var{top-margin}, +@var{bottom-margin}, @var{left-margin}, and @var{right-margin} +arguments may be used. Refer to @code{draw-sprite} (@pxref{Sprites}) for information about -the other arguments. +the other arguments as they are the same. @end deffn @node Fonts diff --git a/examples/9-patch.scm b/examples/9-patch.scm new file mode 100644 index 0000000..451f079 --- /dev/null +++ b/examples/9-patch.scm @@ -0,0 +1,17 @@ +(use-modules (chickadee) + (chickadee math rect) + (chickadee math vector) + (chickadee graphics font) + (chickadee graphics 9-patch) + (chickadee graphics texture)) + +(define image #f) + +(define (load) + (set! image (load-image "images/dialog-box.png"))) + +(define (draw alpha) + (draw-9-patch image (make-rect 192.0 192.0 256.0 96.0) #:margin 4.0) + (draw-text "I am error." #v(200.0 266.0))) + +(run-game #:load load #:draw draw) diff --git a/examples/nine-patch.scm b/examples/nine-patch.scm deleted file mode 100644 index d8988c8..0000000 --- a/examples/nine-patch.scm +++ /dev/null @@ -1,17 +0,0 @@ -(use-modules (chickadee) - (chickadee math rect) - (chickadee math vector) - (chickadee graphics font) - (chickadee graphics sprite) - (chickadee graphics texture)) - -(define image #f) - -(define (load) - (set! image (load-image "images/dialog-box.png"))) - -(define (draw alpha) - (draw-nine-patch image (make-rect 192.0 192.0 256.0 96.0) #:margin 6) - (draw-text "I am error." #v(200.0 266.0))) - -(run-game #:load load #:draw draw) -- cgit v1.2.3