Generare un file immagine da una DrawingArea

Da Gambas-it.org - Wikipedia.

Per generare un file immagine dal disegno di una DrawingArea, si possono adottare le seguenti modalità:

  • utilizzare le sole funzioni di Gambas;
  • utilizzare le funzioni esterne della libreria di X11;
  • utilizzare le funzioni esterne delle librerie di X11 e di Imlib2.


Utilizzare le sole funzioni di Gambas

L'uso delle sole funzioni fornite dirattemente da Gambas consente di adottare almeno quattro modalità.

1a modalità

La prima modalità è la più semplice fra quelle previste in questa pagina. Viene utilizzata la Classe DesktopWindow; e pertanto sarà necessario attivare i Componenti gb.desktop e gb.desktop.x11 .

Mostriamo un esempio pratico:

Public Sub Form_Open()
 
 With DrawingArea1
   .Background = Color.White
   .W = 300
   .H = 40
 End With
  
End


Public Sub DrawingArea1_Draw()
 
 Dim c As Integer[] = [Color.Blue, Color.Green, Color.Yellow, Color.Red]
 Dim p As Float[] = [0, 0.34, 0.67, 1]
  
' Disegnamo all'interno della "DrawingArea":
  With Paint
    .Brush = .LinearGradient(0, 0, DrawingArea1.W, DrawingArea1.H, c, p)
    .Rectangle(0, 0, DrawingArea1.W, DrawingArea1.H)
    .Fill
   .End
  End With
  
End


Public Sub Button1_Click()
 
 Dim dw As DesktopWindow
 Dim pc As Picture
 
  With dw = New DesktopWindow(DrawingArea1.Handle)
    pc = .GetScreenshot(True)
  End With
   
  pc.Save("/tmp/immagine.png", 100)
  
End


2a modalità

La seconda modalità è molto simile alla precedente con la differenza che viene utilizzato il Metodo ".Screenshot( )" della Classe "Desktop".

Mostriamo un esempio pratico:

Public Sub Form_Open()
 
 With DrawingArea1
   .Background = Color.White
   .W = 300
   .H = 40
 End With
  
End


Public Sub DrawingArea1_Draw()
 
 Dim c As Integer[] = [Color.Blue, Color.Green, Color.Yellow, Color.Red]
 Dim p As Float[] = [0, 0.34, 0.67, 1]
  
' Disegnamo all'interno della "DrawingArea":
  With Paint
    .Brush = .LinearGradient(0, 0, DrawingArea1.W, DrawingArea1.H, c, p)
    .Rectangle(0, 0, DrawingArea1.W, DrawingArea1.H)
    .Fill
   .End
  End With
  
End


Public Sub Button1_Click()
 
 Dim pc As Picture
  
  pc = Desktop.Screenshot(DrawingArea1.ScreenX, DrawingArea1.ScreenY, DrawingArea1.Width, DrawingArea1.Height)
   
  pc.Save("/tmp/immagine.png", 100)
  
End


3a modalità

La terza modalità richiede un procedimento più complesso, che può essere così brevemente esplicitato:

  • disegno e/o scrivo nella DrawingArea;
  • creo il file PDF mediante la proprietà .OutputFile della Classe Printer;
  • utilizzando il file PDF, converto la pagina PDF in una Image (è necessario attivare il componente gb.pdf);
  • quindi salvo detta Image con il suo metodo ".Save( )" in un file immagine.


Mostriamo di seguito un esempio:

Public Sub DrawingArea1_Draw()
 
  With Paint
    .Brush = Paint.Color(Color.Red)
    .MoveTo(200, 200)
    .RelLineTo(0, 100)
    .Stroke
    .DrawText("Testo con DrawText", 10, 10, 20, 20)
    .End
  End With
    
End


Public Sub Button1_Click()

  With Printer1
    .Configure
    .Orientation = 0
    .Paper = 2
    .Resolution = Desktop.Resolution
' Stampa/crea un file .pdf:
    .OutputFile = "/tmp/mio_file.pdf"
    .Print
  End With

End


Public Sub Printer1_Draw()

  DrawingArea1_Draw()

End


Public Sub Button2_Click()

 Dim pdf As New PdfDocument
 Dim i As New Image

  
  With pdf
    .Open("/tmp/mio_file.pdf")
' Se il file pdf è stato caricato correttamente...
    If .Ready = True Then
' ...allora convertiamo la prima pagina ("indice" delle pagine = 1) in una "Image":
      i = pdf[1].Image

' Infine salviamo l'Image in un file immagine
' (è necessario scegliere il tipo di file immagine fra quelli supportati):
      i.Save("/tmp/mio_file_immagine.xxx")

    Else
      Message.Error("<FONT color=darkred>Errore nel caricamento del file PDF !")
   
    Endif

  End With

End


4a modalità

La quarta modalità ha una procedura più semplice, ma si basa sostanzialmente nel disegnare la medesima cosa sia nella DrawingArea che in un distinto oggetto Image, che poi verrà salvato. Pertanto, tale modalità appare una soluzione poco raffinata.

Mostriamo un semplice esempio pratico:

Public Sub DrawingArea1_Draw()
  
  Disegno()
  
End
 

Public Sub Button1_Click()
 
 Dim im As Image
 
' Crea un'immagine delle medesime dimensioni della "DrawingArea":
  im = New Image(DrawingArea1.Width, DrawingArea1.Height, Color.White)
 
  With Paint
    .Begin(im)
    Disegno()
  End With
 
  im.Save("/tmp/immagine.png", 100)
  
End


Private Sub Disegno()   ' Imposta il medesimo disegno sia per la "DrawingArea" che per l'oggetto "Image"
 
  With Paint
    .Brush = Paint.Color(Color.Red)
    .Ellipse(100, 10, 100, 100)
    .Stroke
    .End
  End With
  
End


Uso delle funzioni esterne del API di X11

L'uso delle risorse della libreria esterna X11, opportunamente combinate con quelle di Gambas, consente un procedimento molto più snello ripetto al precedente. Tale modalità richiede nel codice Gambas la dichiarazione della libreria dinamica condivisa: "libX11.so.6.3.0"

Sussistono almeno due modalità.
Al fine di evitare che la funzione esterna "XGetImage( )" ritorni un Puntatore nullo, bisognerà avere massima cura che le dimensioni del Form siano sufficientemente superiori alle dimensioni della DrawingArea contenuta.

1a modalità

La prima modalità prevede l'assegnazione dei dati dei pixel della DrawingArea, contenuti nell'area di memoria puntata dal Puntatore restituito dalla funzione esterna XGetImage( ) della libreria di X11, direttamente all'area di memoria puntata dalla proprietà .Data di un oggetto Image preliminarmente creato.


Mostriamo un esempio pratico (è necessario porre sul Form una DrawingArea e un Button):

Library "libX11:6.3.0"

Public Struct funcs
  create_image As Pointer
  destroy_image As Pointer
  get_pixel As Pointer
  put_pixel As Pointer
  sub_image As Pointer
  add_pixel As Pointer
End Struct

Public Struct XImage
  width As Integer
  height As Integer
  xoffset As Integer
  gformat As Integer
  data As Pointer
  byte_order As Integer
  bitmap_unit As Integer
  bitmap_bit_order As Integer
  bitmap_pad As Integer
  depth As Integer
  bytes_per_line As Integer
  bits_per_pixel As Integer
  red_mask As Long
  green_mask As Long
  blue_mask As Long
  obdata As Pointer
  func As Struct Funcs
End Struct

Private Enum XYBitmap = 0, XYPixmap, ZPixmap

' Display *XOpenDisplay(char *display_name)
' Opens a connection to the X server that controls a display.
Private Extern XOpenDisplay(display_name As Pointer) As Pointer

' unsigned long XAllPlanes()
' Returns a value with all bits set to 1 suitable for use in a plane argument to a procedure.
Private Extern XAllPlanes() As Long

' XImage *XGetImage(Display *display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format)
' Returns a pointer to an XImage structure.
Private Extern XGetImage(display As Pointer, d As Long, x As Integer, y As Integer, width As Integer, height As Integer, plane_mask As Long, xformat As Integer) As XImage
 
' XCloseDisplay(Display *display)
' Closes the connection to the X server for the display specified in the Display structure and destroys all windows.
Private Extern XCloseDisplay(display As Pointer)


Public Sub Form_Open()

' Imposta alcune proprietà della "DrawingArea":
 With DrawingArea1
   .X = 10
   .Y = 10
   .W = 400
   .H = 300
   .Background = Color.White
 End With
 
End


Public Sub DrawingArea1_Draw()
 
 Dim colori As Integer[] = [Color.Blue, Color.Green, Color.Yellow, Color.Red]
 Dim p As Float[] = [0, 0.34, 0.67, 1]
  
' Disegnamo all'interno della "DrawingArea":
  With Paint
    .Brush = .LinearGradient(10, 40, 300, 40, colori, p)
    .Rectangle(10, 40, 300, 40)
    .Fill
' Imposta il colore dei caratteri:
    .Brush = .Color(Color.Red) 
' Imposta il tipo di carattere:
   .Font.Name = "Arial" 
' Imposta la dimensione dei caratteri:
   .Font.Size = 16
' Scrive sull'Area di Disegno il seguente testo di caratteri nella posizione X, Y all'interno di un quadrilatero virtuale W, H
' con i metodi .RichText  e .Text, ma occorrerà uno ".Stroke" a seguire (però .Stroke tende a dare l'effetto del grassetto):
   .RichText("Testo con RichText", 200, 20, 16, 100)
   .Text("Testo con Text", 10, 30, 20, 20)
   .Stroke
' ...oppure (meglio, perché non dà l'effetto del grassetto) ".fill":
   .Fill
' Con il metodo .DrawText non è necessario l'uso di ".Stroke", né di ".Fill":
   .DrawText("Testo con DrawText", 10, 10, 20, 20)
   .End
 End With
  
End


Public Sub Button1_Click()

 Dim dsp As Pointer
 Dim xi As XImage
 Dim im As Image
 Dim st As Stream
 Dim i As Integer
  
  dsp = XOpenDisplay(0)
  If dsp = 0 Then Error.Raise("Impossibile aprire una connessione al server X !")
  
' Otteniamo un puntatore alla "Struttura" contenente i dati dell'immagine disegnata nella "DrawingArea":
  xi = XGetImage(dsp, DrawingArea1.Handle, 0, 0, DrawingArea1.W, DrawingArea1.H, XAllPlanes(), ZPixmap)
  If IsNull(Image) Then Error.Raise("Impossibile ottenere un 'Puntatore' ai dati dell'immagine della DrawingArea !")
     
' Creiamo un semplice oggetto di tipo "Image":
  im = New Image(DrawingArea1.W, DrawingArea1.H)
  If IsNull(im) Then Error.Raise("Impossibile creare un oggetto 'Image' !")
  
' Utilizziamo ovviamente i "Memory Stream" per scrivere nell'area di memoria dell'oggetto "Image", destinata ai dati
' attinenti ai pixel, il cui indirizzo di memoria è ritornato dalla proprietà ".Data" della variabile di tipo "Image":
  st = Memory im.Data For Write
  
  For i = 0 To im.W * im.H * Len(im.Format)
    Write #st, Byte@(xi.data + i) As Byte
  Next
  
  st.Close
 
' Genera in fine il file immagine:
  im.Save("/percorso/del/file/immagine.xxx", 100)
  
' Va in chiusura:
  XCloseDisplay(dsp)
  
End


2a modalità

La seconda modalità prevede l'assegnazione dei dati dei pixel della DrawingArea all'area di memoria puntata dal membro "*data" della Struttura "GB_IMG" contenuta nel file sorgente ".../main/lib/image/gb.image.h". [nota 2]

Mostriamo di seguito un semplice esempio, nel quale disegneremo all'interno di una DrawingArea una riga colorata con tutta la gamma dei colori dal blu al rosso. Al termine salveremo in un file immagine il disegno presente nella DrawingArea.

Library "libX11:6.3.0"

Public Struct funcs
  create_image As Pointer
  destroy_image As Pointer
  get_pixel As Pointer
  put_pixel As Pointer
  sub_image As Pointer
  add_pixel As Pointer
End Struct

Public Struct XImage
  width As Integer
  height As Integer
  xoffset As Integer
  gformat As Integer
  data As Pointer
  byte_order As Integer
  bitmap_unit As Integer
  bitmap_bit_order As Integer
  bitmap_pad As Integer
  depth As Integer
  bytes_per_line As Integer
  bits_per_pixel As Integer
  red_mask As Long
  green_mask As Long
  blue_mask As Long
  obdata As Pointer
  func As Struct Funcs
End Struct

Private Enum XYBitmap = 0, XYPixmap, ZPixmap

' Display *XOpenDisplay(char *display_name)
' Opens a connection to the X server that controls a display.
Private Extern XOpenDisplay(display_name As Pointer) As Pointer

' unsigned long XAllPlanes()
' Returns a value with all bits set to 1 suitable for use in a plane argument to a procedure.
Private Extern XAllPlanes() As Long

' XImage *XGetImage(Display *display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format)
' Returns a pointer to an XImage structure.
Private Extern XGetImage(displayP As Pointer, d As Long, x As Integer, y As Integer, width As Integer, height As Integer, plane_mask As Long, xformat As Integer) As XImage
 
' XCloseDisplay(Display *display)
' Closes the connection to the X server for the display specified in the Display structure and destroys all windows.
Private Extern XCloseDisplay(display As Pointer)

 
Public Sub Form_Open()
  
 DrawingArea1.Background = Color.White

End


Public Sub DrawingArea1_Draw()
  
 Dim c As Integer[] = [Color.Blue, Color.Green, Color.Yellow, Color.Red]
 Dim p As Float[] = [0, 0.34, 0.67, 1]
   
' Disegnamo all'interno della "DrawingArea":
  With Paint
    .Brush = .LinearGradient(10, 40, 300, 40, c, p)
    .Rectangle(10, 40, 300, 40)
    .Fill
    .End
  End With

End


Public Sub Button1_Click()
 
 Dim dsp, p, p1 As Pointer
 Dim xi As XImage
 Dim bb As Byte[]
 Dim i As Integer
 Dim st As Stream
 Dim im As Image
   
  dsp = XOpenDisplay(0)
  If dsp = 0 Then Error.Raise("Impossibile aprire una connessione al server X !")
   
' Otteniamo un puntatore alla "Struttura" contenente i dati dell'immagine disegnata nella "DrawingArea":
  xi = XGetImage(dsp, DrawingArea1.Handle, 0, 0, DrawingArea1.W, DrawingArea1.H, XAllPlanes(), ZPixmap)
  If IsNull(xi) Then Error.Raise("Impossibile ottenere un 'Puntatore' ai dati dell'immagine della DrawingArea !")
    
  bb = New Byte[]    
  
  For i = 0 To (DrawingArea1.W * DrawingArea1.H * 4) - 1
    bb.Push(Byte@(xi.data + i))
  Next
   
  im = New Image(DrawingArea1.W, DrawingArea1.H)
   
  p = Object.Address(im)
   
  p1 = Pointer@(p + (SizeOf(gb.Pointer) * 2))   [Nota 1]
   
' Scriviamo i dati immagine grezzi, ottenuti dalla funzione XGetImage(), nell'area di memoria
' puntata dal membro "*data" della Struttura "GB_IMG" contenuta nel file sorgente ".../main/lib/image/gb.image.h":
  st = Memory p1 For Write
  bb.Write(st, 0, bb.Count)
  st.Close
  
' Generiamo dunque il file immagine:
  im.Save("/percorso/dove/salvare/il/file/immagine.xxx")
 
' Va in chiusura:
  XCloseDisplay(dsp)

End

Uso delle funzioni esterne del API di X11 e di ImLib2

L'uso delle risorse delle librerie esterne X11 e ImLib2 richiede nel codice Gambas la dichiarazione delle seguenti librerie dinamiche condivise:

  • "libX11.so.6.3.0"
  • "libImlib2.so.1.4.7"

Mostriamo di seguito un semplice esempio, nel quale disegneremo all'interno di una DrawingArea una riga colorata con tutta la gamma dei colori dal blu al rosso. Al termine salveremo in un file immagine il disegno presente nella DrawingArea.
Al fine di evitare che la funzione esterna "XGetImage( )" ritorni un Puntatore nullo, bisognerà avere massima cura che le dimensioni del Form siano sufficientemente superiori alle dimensioni della DrawingArea contenuta.

Library "libX11:6.3.0"

Public Struct funcs
  create_image As Pointer
  destroy_image As Pointer
  get_pixel As Pointer
  put_pixel As Pointer
  sub_image As Pointer
  add_pixel As Pointer
End Struct

Public Struct XImage
  width As Integer
  height As Integer
  xoffset As Integer
  gformat As Integer
  data As Pointer
  byte_order As Integer
  bitmap_unit As Integer
  bitmap_bit_order As Integer
  bitmap_pad As Integer
  depth As Integer
  bytes_per_line As Integer
  bits_per_pixel As Integer
  red_mask As Long
  green_mask As Long
  blue_mask As Long
  obdata As Pointer
  func As Struct Funcs
End Struct

Private Enum XYBitmap = 0, XYPixmap, ZPixmap

' Display *XOpenDisplay(char *display_name)
' Opens a connection to the X server that controls a display.
Private Extern XOpenDisplay(display_name As Pointer) As Pointer

' unsigned long XAllPlanes()
' Returns a value with all bits set to 1 suitable for use in a plane argument to a procedure.
Private Extern XAllPlanes() As Long

' XImage *XGetImage(Display *display, Drawable d, int x, int y, unsigned int width, unsigned int height, unsigned long plane_mask, int format)
' Returns a pointer to an XImage structure.
Private Extern XGetImage(display As Pointer, d As Long, x As Integer, y As Integer, width As Integer, height As Integer, plane_mask As Long, xformat As Integer) As XImage
 
' XCloseDisplay(Display *display)
' Closes the connection to the X server for the display specified in the Display structure and destroys all windows.
Private Extern XCloseDisplay(display As Pointer)


Library "libImlib2:1.4.7"

' Imlib_Image imlib_create_image_using_data(int width, int height, DATA32 * data)
' Creates an image from the image data - in the format of a DATA32 (32 bits) per pixel in a linear array - specified with the width and the height specified.
Private Extern imlib_create_image_using_data(width As Integer, height As Integer, data As Pointer) As Pointer

' void imlib_context_set_image(Imlib_Image image)
' Sets the current image Imlib2 will be using with its function calls.
Private Extern imlib_context_set_image(Iimage As Pointer)

' void imlib_image_set_has_alpha(char has_alpha)
' Sets the alpha flag for the current image.
Private Extern imlib_image_set_has_alpha(has_alpha As Boolean)

' void imlib_save_image(const char *filename)
' Saves the current image in the format specified by the current image's format settings to the filename.
Private Extern imlib_save_image(filename As String)


Public Sub Form_Open()
  
 DrawingArea1.Background = Color.White

End


Public Sub DrawingArea1_Draw()
  
 Dim c As Integer[] = [Color.Blue, Color.Green, Color.Yellow, Color.Red]
 Dim p As Float[] = [0, 0.34, 0.67, 1]
   
' Disegnamo all'interno della "DrawingArea":
  With Paint
    .Brush = .LinearGradient(10, 40, 300, 40, c, p)
    .Rectangle(10, 40, 300, 40)
    .Fill
    .End
  End With

End


Public Sub Button1_Click()

 Dim dsp, imago As Pointer
 Dim xi As XImage
   
  dsp = XOpenDisplay(0)
  If dsp = 0 Then Error.Raise("Impossibile aprire una connessione al server X !")
   
' Otteniamo un puntatore alla "Struttura" contenente i dati dell'immagine disegnata nella "DrawingArea":
  xi = XGetImage(dsp, DrawingArea1.Handle, 0, 0, DrawingArea1.W, DrawingArea1.H, XAllPlanes(), ZPixmap)
  If IsNull(xi) = 0 Then Error.Raise("Impossibile ottenere un 'Puntatore' ai dati dell'immagine della DrawingArea !")
     
' Viene creata l'immagine dai dati grezzi passando il "Puntatore" ai dati grezzi dell'immagine:
  imago = imlib_create_image_using_data(DrawingArea1.W, DrawingArea1.H, xi.data)
  If imago = 0 Then Error.Raise("Impossibile creare l'immagine dai dati grezzi acquisiti !")
   
  imlib_context_set_image(imago)
   
' L'immagine creata viene salvata in un file immagine. Al nome del file immagine da creare va indicata l'estensione del formato immagine desiderato:
  imlib_save_image("/percorso/dove/salvare/il/file/immagine.xxx")
 
' Va in chiusura:
  XCloseDisplay(dsp)

End

Note

[1] Spieghiamo brevemente perché sia necessario spostarsi al byte, di indice pari al valore restituito da SizeOf(gb.Pointer), dell'area di memoria puntata dal Puntatore XImage.
Bisogna innanzitutto tenere in considerazione la omonima Struttura XImage restituita dalla funzione "XGetImage()", che può essere presa in visione qui:
http://tronche.com/gui/x/xlib/graphics/images.html#XImage
Ebbene, il quinto membro di detta Struttura

char *data;............/* pointer to image data */

è una variabile Puntatore di tipo char, che contiene l'indirizzo dell'area di memoria ove sono stipati i dati dell'immagine mostrata dalla DrawingArea.
Va precisato che, come evidente, nell'esempio in Gambas noi operiamo semplicemente sull'area di memoria riservata dalla predetta funzione "XGetImage( )" e puntata dall'apposita variabile XImage. Insomma, non abbiamo passato una variabile di tipo Struttura (come in realtà è previsto nella libreria esterna di X11), ma semplicemente un Puntatore all'area di memoria riservata coerentemente corrispondente a quella della omonima Struttura prevista dalla funzione "XGetImage( )".
Perché dobbiamo spostarci al byte di indice uguale al doppio dei byte di memoria, occupati da una variabile di tipo Puntatore, per leggere il valore di quell'indirizzo di memoria ?
La risposta è nei quattro membri precedenti, i quali sono tutti di tipo Intero, e pertanto occupano ciascuno un numero di byte di memoria pari a SizeOf(gb.Integer) e complessivamente: SizeOf(gb.Integer) + SizeOf(gb.Integer) = SizeOf(gb.Pointer).
Il valore del Puntatore (ossia l'indirizzo di memoria dei dati grezzi dell'immagine puntati dalla omonima Struttura "XImage" della libreria esterna di X11), che a noi interessa, si trova a cominciare dal byte di indice = SizeOf(gb.Pointer). Il valore dell'indirizzo di memoria verrà da noi salvato nel tipo di variabile a ciò deputato, ossia il Puntatore, e così passato alla successiva funzione "imlib_create_image_using_data()", come da essa richiesto.

[2] Al riguardo vedasi anche la pagina della Wiki: Gestire un oggetto Image agendo direttamente sulle risorse dei sorgenti Gambas



Riferimenti