new cairo image example
This commit is contained in:
parent
e9082e0efe
commit
e0a619eff5
2 changed files with 336 additions and 0 deletions
176
README.adoc
176
README.adoc
|
|
@ -1640,5 +1640,181 @@ proc main =
|
|||
main()
|
||||
----
|
||||
|
||||
== One more cairo example
|
||||
|
||||
Recently Mr. C. Eric Cashon provided an example code for working with a large bitmap image.
|
||||
His example writes the image to disk, loads it again and displays the image allowing
|
||||
zooming and translation. As examples are rare in these days, and that example is not to large,
|
||||
I used c2nim to convert it to Nim. Below is the code with a few manually fixes. Note, the
|
||||
current shipped cairo.nim module contains an assert statement, which prevents running this example.
|
||||
If you really intent running this code, you will have to fix that single line in cairo.nim. I
|
||||
have to do some more fixes in the cairo module and may ship a new version eventually. This
|
||||
example is really low level, as alloc() is used directly.
|
||||
|
||||
[[cairoImage.nim]]
|
||||
[source,nim]
|
||||
.cairoImage.nim
|
||||
----
|
||||
# https://discourse.gnome.org/t/proper-zoom-pan-image-approach-for-large-images/1497/6
|
||||
# Nim version of the C example of C. Eric Cashon
|
||||
import gintro/[gtk, gobject, glib, cairo]
|
||||
from math import TAU
|
||||
import strutils
|
||||
|
||||
const
|
||||
Width = 5000
|
||||
Height = 5000
|
||||
CFormat = cairo.Format.argb32
|
||||
|
||||
var
|
||||
Key: cairo.UserDataKey
|
||||
translateX: float
|
||||
translateY: float
|
||||
scale = 1.0
|
||||
## Store data from file.
|
||||
bigSurfaceData*: ptr cuchar = nil
|
||||
|
||||
proc translateXSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
translateX = spinButton.value
|
||||
data.queueDraw
|
||||
|
||||
proc translateYSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
translateY = spinButton.getValue
|
||||
data.queueDraw
|
||||
|
||||
proc scaleSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
scale = spinButton.value
|
||||
data.queueDraw
|
||||
|
||||
proc saveBigSurface =
|
||||
## Use gdk_cairo_surface_create_from_pixbuf() to read in a pixbuf. Try a test surface here.
|
||||
let bigSurface = imageSurfaceCreate(CFormat, Width, Height)
|
||||
let cr = newContext(bigSurface)
|
||||
## Paint the background.
|
||||
cr.setSource(1, 1, 1)
|
||||
cr.paint
|
||||
## Draw a circle.
|
||||
cr.setSource(0, 0, 1)
|
||||
cr.arc(250, 250, 50, 0, math.TAU)
|
||||
cr.fill
|
||||
## Draw some test grid lines.
|
||||
cr.setSource(0, 1, 0)
|
||||
for i in countup(0, 4900, 100):
|
||||
cr.moveTo(0, i.float)
|
||||
cr.lineTo(5000, i.float)
|
||||
cr.stroke
|
||||
for i in countup(0, 4900, 100):
|
||||
cr.moveTo(i.float, 0)
|
||||
cr.lineTo(i.float, 5000)
|
||||
cr.stroke
|
||||
cr.setSource(0, 0, 1)
|
||||
cr.setLineWidth(10)
|
||||
for i in 0 ..< 10:
|
||||
cr.moveTo(0, i.float * 500.0)
|
||||
cr.lineTo(5000, i.float * 500.0)
|
||||
cr.stroke
|
||||
for i in 0 ..< 10:
|
||||
cr.moveTo(i.float * 500.0, 0)
|
||||
cr.lineTo(i.float * 500.0, 5000)
|
||||
cr.stroke
|
||||
## Outside box.
|
||||
cr.setLineWidth(20)
|
||||
cr.setSource(1, 0, 1)
|
||||
cr.rectangle(0, 0, 5000, 5000)
|
||||
cr.stroke
|
||||
## Save surface data to file.
|
||||
let f: File = open("big_surface.s", fmWrite)
|
||||
let p: ptr cuchar = cairo_image_surface_get_data(bigSurface.impl)
|
||||
let len = writeBuffer(f, p, cairo_format_stride_for_width(CFormat, Width) * Height)
|
||||
echo("write $1\n" % $len)
|
||||
close(f)
|
||||
|
||||
proc myDealloc(data: pointer) {.cdecl.} =
|
||||
system.dealloc(data)
|
||||
|
||||
proc getBigSurface(): Surface =
|
||||
let f: File = open("big_surface.s", fmRead)
|
||||
# setFilePos(f, 0)
|
||||
# https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-stride-for-width
|
||||
let stride = cairo_format_stride_for_width(CFormat, Width)
|
||||
bigSurfaceData = cast[ptr cuchar](malloc((stride * Height).uint64))
|
||||
var len = readBuffer(f, bigSurfaceData, stride * Height)
|
||||
echo("read $1" % $len)
|
||||
close(f)
|
||||
let bigSurface: Surface = new Surface # this is a temporary fix, we will support this later in cairo modul
|
||||
bigSurface.impl = cairo_image_surface_create_for_data(bigSurfaceData, CFormat, Width, Height, stride)
|
||||
discard setUserData(bigSurface, addr(Key), bigSurfaceData, myDealloc) # automatic deallocation
|
||||
# flush(bigSurface)
|
||||
echo("open $1" % bigSurface.status.statusToString)
|
||||
return bigSurface
|
||||
|
||||
proc daDrawing*(da: DrawingArea; cr: Context; bigSurface: Surface): bool =
|
||||
var
|
||||
width = da.getAllocatedWidth.float
|
||||
height = da.getAllocatedHeight.float
|
||||
originX = translateX
|
||||
originY = translateY
|
||||
## Some constraints.
|
||||
if translateX > 5000.0 - width:
|
||||
originX = 5000.0 - width / scale
|
||||
if translateY > 5000.0 - height:
|
||||
originY = 5000.0 - height / scale
|
||||
cr.setSource(0, 0, 0)
|
||||
cr.paint
|
||||
## Partition the big surface.
|
||||
var littleSurface: Surface = cairo.surfaceCreateForRectangle(bigSurface,
|
||||
originX, originY, width / scale, height / scale)
|
||||
cr.scale(scale, scale)
|
||||
cr.setSourceSurface(littleSurface, 0, 0)
|
||||
setFilter(getSource(cr), cairo.Filter.bilinear)
|
||||
cr.paint
|
||||
return true
|
||||
|
||||
proc bye(w: Window) =
|
||||
mainQuit()
|
||||
echo "Bye..."
|
||||
|
||||
proc main =
|
||||
gtk.init()
|
||||
let window = newWindow()
|
||||
window.setTitle("Big Surface2")
|
||||
window.setDefaultSize(500, 500)
|
||||
window.setPosition(gtk.WindowPosition.center)
|
||||
window.connect("destroy", bye)
|
||||
## Get a test surface.
|
||||
saveBigSurface()
|
||||
let bigSurface = getBigSurface()
|
||||
let da: DrawingArea = newDrawingArea()
|
||||
da.setHexpand
|
||||
da.setVexpand
|
||||
da.connect("draw", daDrawing, bigSurface)
|
||||
let
|
||||
translateXAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
|
||||
translateYAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
|
||||
scaleAdj = newAdjustment(1, 1, 5, 0.1, 0, 0)
|
||||
translateXLabel = newLabel("translate x")
|
||||
translateXSpin= newSpinButton(translateXAdj, 50, 1)
|
||||
connect(translateXSpin, "value-changed", translateXSpinChanged, da)
|
||||
let translateYLabel = newLabel("translate y")
|
||||
let translateYSpin = newSpinButton(translateYAdj, 50, 1)
|
||||
connect(translateYSpin, "value-changed", translateYSpinChanged, da)
|
||||
let scaleLabel = newLabel("Scale")
|
||||
let scaleSpin = newSpinButton(scaleAdj, 0.2, 1)
|
||||
connect(scaleSpin, "value-changed", scaleSpinChanged, da)
|
||||
let grid = newGrid()
|
||||
grid.attach(da, 0, 0, 3, 1)
|
||||
grid.attach(translateXLabel, 0, 1, 1, 1)
|
||||
grid.attach(translateYLabel, 1, 1, 1, 1)
|
||||
grid.attach(scaleLabel, 2, 1, 1, 1)
|
||||
grid.attach(translateXSpin, 0, 2, 1, 1)
|
||||
grid.attach(translateYSpin, 1, 2, 1, 1)
|
||||
grid.attach(scaleSpin, 2, 2, 1, 1)
|
||||
add(window, grid)
|
||||
showAll(window)
|
||||
gtk.main()
|
||||
|
||||
main()
|
||||
----
|
||||
|
||||
NOTE: Related work: https://github.com/jdmansour/nim-smartgi
|
||||
|
||||
|
|
|
|||
160
examples/gtk3/cairoImage.nim
Normal file
160
examples/gtk3/cairoImage.nim
Normal file
|
|
@ -0,0 +1,160 @@
|
|||
# https://discourse.gnome.org/t/proper-zoom-pan-image-approach-for-large-images/1497/6
|
||||
# Nim version of the C example of C. Eric Cashon
|
||||
import gintro/[gtk, gobject, glib, cairo]
|
||||
from math import TAU
|
||||
import strutils
|
||||
|
||||
const
|
||||
Width = 5000
|
||||
Height = 5000
|
||||
CFormat = cairo.Format.argb32
|
||||
|
||||
var
|
||||
Key: cairo.UserDataKey
|
||||
translateX: float
|
||||
translateY: float
|
||||
scale = 1.0
|
||||
## Store data from file.
|
||||
bigSurfaceData*: ptr cuchar = nil
|
||||
|
||||
proc translateXSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
translateX = spinButton.value
|
||||
data.queueDraw
|
||||
|
||||
proc translateYSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
translateY = spinButton.getValue
|
||||
data.queueDraw
|
||||
|
||||
proc scaleSpinChanged(spinButton: SpinButton; data: DrawingArea) =
|
||||
scale = spinButton.value
|
||||
data.queueDraw
|
||||
|
||||
proc saveBigSurface =
|
||||
## Use gdk_cairo_surface_create_from_pixbuf() to read in a pixbuf. Try a test surface here.
|
||||
let bigSurface = imageSurfaceCreate(CFormat, Width, Height)
|
||||
let cr = newContext(bigSurface)
|
||||
## Paint the background.
|
||||
cr.setSource(1, 1, 1)
|
||||
cr.paint
|
||||
## Draw a circle.
|
||||
cr.setSource(0, 0, 1)
|
||||
cr.arc(250, 250, 50, 0, math.TAU)
|
||||
cr.fill
|
||||
## Draw some test grid lines.
|
||||
cr.setSource(0, 1, 0)
|
||||
for i in countup(0, 4900, 100):
|
||||
cr.moveTo(0, i.float)
|
||||
cr.lineTo(5000, i.float)
|
||||
cr.stroke
|
||||
for i in countup(0, 4900, 100):
|
||||
cr.moveTo(i.float, 0)
|
||||
cr.lineTo(i.float, 5000)
|
||||
cr.stroke
|
||||
cr.setSource(0, 0, 1)
|
||||
cr.setLineWidth(10)
|
||||
for i in 0 ..< 10:
|
||||
cr.moveTo(0, i.float * 500.0)
|
||||
cr.lineTo(5000, i.float * 500.0)
|
||||
cr.stroke
|
||||
for i in 0 ..< 10:
|
||||
cr.moveTo(i.float * 500.0, 0)
|
||||
cr.lineTo(i.float * 500.0, 5000)
|
||||
cr.stroke
|
||||
## Outside box.
|
||||
cr.setLineWidth(20)
|
||||
cr.setSource(1, 0, 1)
|
||||
cr.rectangle(0, 0, 5000, 5000)
|
||||
cr.stroke
|
||||
## Save surface data to file.
|
||||
let f: File = open("big_surface.s", fmWrite)
|
||||
let p: ptr cuchar = cairo_image_surface_get_data(bigSurface.impl)
|
||||
let len = writeBuffer(f, p, cairo_format_stride_for_width(CFormat, Width) * Height)
|
||||
echo("write $1\n" % $len)
|
||||
close(f)
|
||||
|
||||
proc myDealloc(data: pointer) {.cdecl.} =
|
||||
system.dealloc(data)
|
||||
|
||||
proc getBigSurface(): Surface =
|
||||
let f: File = open("big_surface.s", fmRead)
|
||||
# setFilePos(f, 0)
|
||||
# https://www.cairographics.org/manual/cairo-Image-Surfaces.html#cairo-format-stride-for-width
|
||||
let stride = cairo_format_stride_for_width(CFormat, Width)
|
||||
bigSurfaceData = cast[ptr cuchar](malloc((stride * Height).uint64))
|
||||
var len = readBuffer(f, bigSurfaceData, stride * Height)
|
||||
echo("read $1" % $len)
|
||||
close(f)
|
||||
let bigSurface: Surface = new Surface # this is a temporary fix, we will support this later in cairo modul
|
||||
bigSurface.impl = cairo_image_surface_create_for_data(bigSurfaceData, CFormat, Width, Height, stride)
|
||||
discard setUserData(bigSurface, addr(Key), bigSurfaceData, myDealloc) # automatic deallocation
|
||||
# flush(bigSurface)
|
||||
echo("open $1" % bigSurface.status.statusToString)
|
||||
return bigSurface
|
||||
|
||||
proc daDrawing*(da: DrawingArea; cr: Context; bigSurface: Surface): bool =
|
||||
var
|
||||
width = da.getAllocatedWidth.float
|
||||
height = da.getAllocatedHeight.float
|
||||
originX = translateX
|
||||
originY = translateY
|
||||
## Some constraints.
|
||||
if translateX > 5000.0 - width:
|
||||
originX = 5000.0 - width / scale
|
||||
if translateY > 5000.0 - height:
|
||||
originY = 5000.0 - height / scale
|
||||
cr.setSource(0, 0, 0)
|
||||
cr.paint
|
||||
## Partition the big surface.
|
||||
var littleSurface: Surface = cairo.surfaceCreateForRectangle(bigSurface,
|
||||
originX, originY, width / scale, height / scale)
|
||||
cr.scale(scale, scale)
|
||||
cr.setSourceSurface(littleSurface, 0, 0)
|
||||
setFilter(getSource(cr), cairo.Filter.bilinear)
|
||||
cr.paint
|
||||
return true
|
||||
|
||||
proc bye(w: Window) =
|
||||
mainQuit()
|
||||
echo "Bye..."
|
||||
|
||||
proc main =
|
||||
gtk.init()
|
||||
let window = newWindow()
|
||||
window.setTitle("Big Surface2")
|
||||
window.setDefaultSize(500, 500)
|
||||
window.setPosition(gtk.WindowPosition.center)
|
||||
window.connect("destroy", bye)
|
||||
## Get a test surface.
|
||||
saveBigSurface()
|
||||
let bigSurface = getBigSurface()
|
||||
let da: DrawingArea = newDrawingArea()
|
||||
da.setHexpand
|
||||
da.setVexpand
|
||||
da.connect("draw", daDrawing, bigSurface)
|
||||
let
|
||||
translateXAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
|
||||
translateYAdj = newAdjustment(0, 0, 5000, 20, 0, 0)
|
||||
scaleAdj = newAdjustment(1, 1, 5, 0.1, 0, 0)
|
||||
translateXLabel = newLabel("translate x")
|
||||
translateXSpin= newSpinButton(translateXAdj, 50, 1)
|
||||
connect(translateXSpin, "value-changed", translateXSpinChanged, da)
|
||||
let translateYLabel = newLabel("translate y")
|
||||
let translateYSpin = newSpinButton(translateYAdj, 50, 1)
|
||||
connect(translateYSpin, "value-changed", translateYSpinChanged, da)
|
||||
let scaleLabel = newLabel("Scale")
|
||||
let scaleSpin = newSpinButton(scaleAdj, 0.2, 1)
|
||||
connect(scaleSpin, "value-changed", scaleSpinChanged, da)
|
||||
let grid = newGrid()
|
||||
grid.attach(da, 0, 0, 3, 1)
|
||||
grid.attach(translateXLabel, 0, 1, 1, 1)
|
||||
grid.attach(translateYLabel, 1, 1, 1, 1)
|
||||
grid.attach(scaleLabel, 2, 1, 1, 1)
|
||||
grid.attach(translateXSpin, 0, 2, 1, 1)
|
||||
grid.attach(translateYSpin, 1, 2, 1, 1)
|
||||
grid.attach(scaleSpin, 2, 2, 1, 1)
|
||||
add(window, grid)
|
||||
showAll(window)
|
||||
gtk.main()
|
||||
|
||||
main()
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue