#include "hbclass.ch" PROCEDURE Main() LOCAL cOutDir := hb_DirBase() + "render_image_output" LOCAL cFull96 := cOutDir + hb_ps() + "full_96.png" LOCAL cFull150 := cOutDir + hb_ps() + "full_150.png" LOCAL cFull300 := cOutDir + hb_ps() + "full_300.png" LOCAL cCrop := cOutDir + hb_ps() + "crop.png" LOCAL cPartial := cOutDir + hb_ps() + "partial.png" LOCAL cEmpty := cOutDir + hb_ps() + "empty.png" LOCAL cJpgCrop := cOutDir + hb_ps() + "crop.jpg" LOCAL cJpgFull := cOutDir + hb_ps() + "full.jpg" LOCAL cJpg50 := cOutDir + hb_ps() + "quality_50.jpg" LOCAL cJpg90 := cOutDir + hb_ps() + "quality_90.jpg" LOCAL cJpg100 := cOutDir + hb_ps() + "quality_100.jpg" LOCAL cBmpCrop := cOutDir + hb_ps() + "crop.bmp" LOCAL cBmpFull := cOutDir + hb_ps() + "full.bmp" LOCAL cCorrectedJpg := cOutDir + hb_ps() + "extension.jpg" LOCAL cLegacyPng := cOutDir + hb_ps() + "legacy.png" LOCAL hIR LOCAL oRender LOCAL aInfo LOCAL aPixel LOCAL nFailures := 0 LOCAL nProgress := 0 LOCAL cPng hb_MemoWrit( hb_DirBase() + "test_render_image_result.log", "" ) hIR := TestIR() oRender := TRenderImage():New() IF ! hb_DirExists( cOutDir ) hb_DirBuild( cOutDir ) ENDIF oRender:lCropToContent := .F. oRender:nDPI := 96 nFailures += AssertTrue( "página completa 96 DPI", oRender:RenderPage( hIR, 1, cFull96 ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cFull96 ) nFailures += AssertEqual( "ancho físico 96 DPI", aInfo[ 1 ], 378 ) nFailures += AssertEqual( "alto físico 96 DPI", aInfo[ 2 ], 227 ) nFailures += AssertTrue( "fondo conserva transparencia", aInfo[ 4 ] > 0, hb_ValToExp( aInfo ) ) nFailures += AssertEqual( "esquina alfa cero", aInfo[ 9 ], 0 ) nFailures += AssertTrue( "hay texto/objetos negros", aInfo[ 7 ] > 0, hb_ValToExp( aInfo ) ) nFailures += AssertTrue( "hay objeto blanco opaco", aInfo[ 6 ] > 0, hb_ValToExp( aInfo ) ) nFailures += AssertTrue( "antialiasing produce alfa parcial", aInfo[ 8 ] > 0, hb_ValToExp( aInfo ) ) nFailures += AssertTrue( "texto negro visible", LLIMG_FILE_REGION_OPAQUE( cFull96, 18, 18, 148, 50 ) > 0, "" ) nFailures += AssertTrue( "texto blanco visible y opaco", LLIMG_FILE_REGION_OPAQUE( cFull96, 29, 59, 137, 92 ) > 0, "" ) nFailures += AssertTrue( "elipse visible", LLIMG_FILE_REGION_OPAQUE( cFull96, 112, 101, 182, 148 ) > 0, "" ) nFailures += AssertTrue( "linea visible", LLIMG_FILE_REGION_OPAQUE( cFull96, 196, 101, 304, 148 ) > 0, "" ) nFailures += AssertTrue( "codigo matricial visible", LLIMG_FILE_REGION_OPAQUE( cFull96, 309, 101, 364, 148 ) > 0, "" ) cPng := hb_MemoRead( cFull96 ) nFailures += AssertEqual( "PNG declara color RGBA", Asc( SubStr( cPng, 26, 1 ) ), 6 ) aPixel := LLIMG_FILE_PIXEL( cFull96, 170, 28 ) nFailures += AssertEqual( "blanco legítimo alfa 255", aPixel[ 4 ], 255 ) nFailures += AssertTrue( "PNG incrustado conserva pixels", LLIMG_FILE_REGION_OPAQUE( cFull96, 15, 162, 76, 208 ) > 0, "" ) aPixel := LLIMG_FILE_PIXEL( cFull96, 121, 185 ) nFailures += AssertEqual( "JPG incrustado es opaco", aPixel[ 4 ], 255 ) oRender:nDPI := 150 nFailures += AssertTrue( "página completa 150 DPI", oRender:RenderPage( hIR, 1, cFull150 ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cFull150 ) nFailures += AssertEqual( "ancho físico 150 DPI", aInfo[ 1 ], 591 ) nFailures += AssertEqual( "alto físico 150 DPI", aInfo[ 2 ], 355 ) oRender:nDPI := 300 nFailures += AssertTrue( "página completa 300 DPI", oRender:RenderPage( hIR, 1, cFull300 ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cFull300 ) nFailures += AssertEqual( "ancho físico 300 DPI", aInfo[ 1 ], 1182 ) nFailures += AssertEqual( "alto físico 300 DPI", aInfo[ 2 ], 709 ) oRender:nDPI := 96 oRender:lCropToContent := .T. oRender:nCropPadding := 0 nFailures += AssertTrue( "recorte al contenido", oRender:RenderPage( CropIR(), 1, cCrop ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cCrop ) nFailures += AssertEqual( "recorte ancho calculado desde IR", aInfo[ 1 ], 77 ) nFailures += AssertEqual( "recorte alto incluye grosor del borde", aInfo[ 2 ], 40 ) nFailures += AssertTrue( "recorte contiene blanco opaco", aInfo[ 6 ] > 0, hb_ValToExp( aInfo ) ) nFailures += AssertTrue( "objeto parcialmente fuera de página", oRender:RenderPage( PartialIR(), 1, cPartial ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cPartial ) nFailures += AssertTrue( "recorte parcial queda dentro de página", aInfo[ 1 ] > 0 .AND. aInfo[ 1 ] <= 39, hb_ValToExp( aInfo ) ) nFailures += AssertTrue( "página vacía", oRender:RenderPage( hIR, 2, cEmpty ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cEmpty ) nFailures += AssertEqual( "página vacía ancho mínimo", aInfo[ 1 ], 1 ) nFailures += AssertEqual( "página vacía alfa cero", aInfo[ 9 ], 0 ) oRender:SetProgressCallback( {| nPage, nTotal, nDataPage, nDataTotal | nProgress := nPage, .T. } ) nFailures += AssertTrue( "exportación multipágina", oRender:RenderAllPages( hIR, cOutDir, "informe" ), oRender:GetLastError() ) nFailures += AssertTrue( "nombre multipágina 001", File( cOutDir + hb_ps() + "informe_001.png" ), "" ) nFailures += AssertTrue( "nombre multipágina 002", File( cOutDir + hb_ps() + "informe_002.png" ), "" ) nFailures += AssertEqual( "progreso multipágina", nProgress, 2 ) oRender:cFormat := "JPG" oRender:nDPI := 96 oRender:cBackgroundColor := "#ffffff" oRender:nJpegQuality := 90 oRender:lCropToContent := .T. nFailures += AssertTrue( "JPG recortado", oRender:RenderPage( CropIR(), 1, cJpgCrop ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cJpgCrop ) nFailures += AssertEqual( "JPG recortado ancho", aInfo[ 1 ], 77 ) nFailures += AssertEqual( "JPG recortado alto", aInfo[ 2 ], 40 ) nFailures += AssertEqual( "JPG sin transparencia", aInfo[ 4 ], 0 ) oRender:lCropToContent := .F. nFailures += AssertTrue( "JPG pagina completa", oRender:RenderPage( hIR, 1, cJpgFull ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cJpgFull ) nFailures += AssertEqual( "JPG completo ancho", aInfo[ 1 ], 378 ) nFailures += AssertEqual( "JPG completo alto", aInfo[ 2 ], 227 ) aPixel := LLIMG_FILE_PIXEL( cJpgFull, 0, 0 ) nFailures += AssertTrue( "JPG compone fondo blanco", aPixel[ 1 ] > 245 .AND. aPixel[ 2 ] > 245 .AND. aPixel[ 3 ] > 245 .AND. aPixel[ 4 ] == 255, hb_ValToExp( aPixel ) ) oRender:lCropToContent := .T. oRender:nJpegQuality := 50 nFailures += AssertTrue( "JPG calidad 50", oRender:RenderPage( hIR, 1, cJpg50 ), oRender:GetLastError() ) oRender:nJpegQuality := 90 nFailures += AssertTrue( "JPG calidad 90", oRender:RenderPage( hIR, 1, cJpg90 ), oRender:GetLastError() ) oRender:nJpegQuality := 100 nFailures += AssertTrue( "JPG calidad 100", oRender:RenderPage( hIR, 1, cJpg100 ), oRender:GetLastError() ) nFailures += AssertTrue( "calidad JPG modifica la salida", hb_FSize( cJpg50 ) != hb_FSize( cJpg90 ) .AND. hb_FSize( cJpg90 ) != hb_FSize( cJpg100 ), "" ) nFailures += AssertTrue( "JPG 100 conserva más información que JPG 50", hb_FSize( cJpg100 ) > hb_FSize( cJpg50 ), "" ) oRender:cFormat := "BMP" oRender:lCropToContent := .T. nFailures += AssertTrue( "BMP recortado", oRender:RenderPage( CropIR(), 1, cBmpCrop ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cBmpCrop ) nFailures += AssertEqual( "BMP recortado ancho", aInfo[ 1 ], 77 ) nFailures += AssertEqual( "BMP recortado alto", aInfo[ 2 ], 40 ) nFailures += AssertEqual( "BMP 24 bits sin transparencia", aInfo[ 4 ], 0 ) oRender:lCropToContent := .F. nFailures += AssertTrue( "BMP pagina completa", oRender:RenderPage( hIR, 1, cBmpFull ), oRender:GetLastError() ) aInfo := LLIMG_FILE_INFO( cBmpFull ) nFailures += AssertEqual( "BMP completo ancho", aInfo[ 1 ], 378 ) nFailures += AssertEqual( "BMP completo alto", aInfo[ 2 ], 227 ) aPixel := LLIMG_FILE_PIXEL( cBmpFull, 0, 0 ) nFailures += AssertTrue( "BMP compone fondo blanco", aPixel[ 1 ] == 255 .AND. aPixel[ 2 ] == 255 .AND. aPixel[ 3 ] == 255 .AND. aPixel[ 4 ] == 255, hb_ValToExp( aPixel ) ) oRender:cFormat := "JPG" oRender:nJpegQuality := 90 oRender:lCropToContent := .T. nFailures += AssertTrue( "multipagina JPG", oRender:RenderAllPages( hIR, cOutDir, "multi_jpg" ), oRender:GetLastError() ) nFailures += AssertTrue( "extension multipagina JPG", File( cOutDir + hb_ps() + "multi_jpg_001.jpg" ) .AND. File( cOutDir + hb_ps() + "multi_jpg_002.jpg" ), "" ) nProgress := 0 nFailures += AssertTrue( "intervalo JPG", oRender:RenderAllPages( hIR, cOutDir, "range_jpg", 2, 2 ), oRender:GetLastError() ) nFailures += AssertTrue( "intervalo genera solo paginas seleccionadas", ! File( cOutDir + hb_ps() + "range_jpg_001.jpg" ) .AND. File( cOutDir + hb_ps() + "range_jpg_002.jpg" ), "" ) nFailures += AssertEqual( "progreso relativo al intervalo", nProgress, 1 ) oRender:cFormat := "BMP" nFailures += AssertTrue( "multipagina BMP", oRender:RenderAllPages( hIR, cOutDir, "multi_bmp" ), oRender:GetLastError() ) nFailures += AssertTrue( "extension multipagina BMP", File( cOutDir + hb_ps() + "multi_bmp_001.bmp" ) .AND. File( cOutDir + hb_ps() + "multi_bmp_002.bmp" ), "" ) oRender:cFormat := "JPEG" nFailures += AssertTrue( "normaliza JPEG y extension", oRender:RenderPage( CropIR(), 1, cOutDir + hb_ps() + "extension.png" ), oRender:GetLastError() ) nFailures += AssertTrue( "archivo corregido a JPG", File( cCorrectedJpg ) .AND. oRender:GetLastOutputFile() == cCorrectedJpg, oRender:GetLastOutputFile() ) nFailures += AssertTrue( "compatibilidad LLDominusExportPNG", LLDominusExportPNG( CropIR(), 1, cOutDir + hb_ps() + "legacy.jpg", 96, .T., 0 ), "" ) nFailures += AssertTrue( "wrapper PNG corrige extension", File( cLegacyPng ), "" ) nFailures += AssertTrue( "wrapper LLDominusExportJPG", LLDominusExportJPG( CropIR(), 1, cOutDir + hb_ps() + "wrapper_jpg.png", 96, .T., 0, "#ffffff", 90 ), "" ) nFailures += AssertTrue( "wrapper JPG corrige extension", File( cOutDir + hb_ps() + "wrapper_jpg.jpg" ), "" ) nFailures += AssertTrue( "wrapper LLDominusExportBMP", LLDominusExportBMP( CropIR(), 1, cOutDir + hb_ps() + "wrapper_bmp.jpg", 96, .T., 0, "#ffffff" ), "" ) nFailures += AssertTrue( "wrapper BMP corrige extension", File( cOutDir + hb_ps() + "wrapper_bmp.bmp" ), "" ) nFailures += AssertTrue( "API generica multipagina PNG", LLDominusExportAllPagesImage( hIR, cOutDir, "generic_png", "PNG", 96, .F., 0, "#ffffff", 90 ), "" ) nFailures += AssertTrue( "API generica crea nombres PNG", File( cOutDir + hb_ps() + "generic_png_001.png" ) .AND. File( cOutDir + hb_ps() + "generic_png_002.png" ), "" ) nFailures += AssertEqual( "canvas liberado tras cada pagina", LLIMG_ACTIVE_CANVASES(), 0 ) oRender:cFormat := "JPG"; oRender:nJpegQuality := 0 nFailures += AssertTrue( "rechaza calidad JPG fuera de rango", ! oRender:RenderPage( CropIR(), 1, cOutDir + hb_ps() + "invalid.jpg" ), oRender:GetLastError() ) oRender:cFormat := "GIF"; oRender:nJpegQuality := 90 nFailures += AssertTrue( "rechaza formato no admitido", ! oRender:RenderPage( CropIR(), 1, cOutDir + hb_ps() + "invalid.gif" ), oRender:GetLastError() ) oRender:cFormat := "PNG" oRender:nDPI := 300 oRender:lCropToContent := .F. oRender:nMaxMemoryMB := 1 nFailures += AssertTrue( "límite de memoria rechaza imagen excesiva", ! oRender:RenderPage( LargeIR(), 1, cOutDir + hb_ps() + "too_large.png" ), oRender:GetLastError() ) nFailures += AssertTrue( "error de memoria es explícito", "supera el límite" $ Lower( oRender:GetLastError() ), oRender:GetLastError() ) oRender:End() oRender := NIL IF nFailures == 0 hb_MemoWrit( hb_DirBase() + "test_render_image_final.log", "OK" ) QOut( "OK: TRenderImage PNG RGBA, recorte, DPI, objetos, imágenes y memoria" ) ErrorLevel( 0 ) ELSE hb_MemoWrit( hb_DirBase() + "test_render_image_final.log", "ERROR:" + hb_NToS( nFailures ) ) QOut( "ERROR: fallos=" + hb_NToS( nFailures ) ) ErrorLevel( 1 ) ENDIF RETURN STATIC FUNCTION TestIR() LOCAL aObjects := {} LOCAL h AAdd( aObjects, TextObject( 5, 5, 34, 8, "áéíóú ÁÉÍÓÚ ñ Ñ º ª €", "#000000", 0 ) ) AAdd( aObjects, TextObject( 8, 16, 28, 8, "Texto blanco", "#ffffff", 0 ) ) h := BaseObject( "rectangulo", 42, 5, 10, 10 ); h[ "colorFondo" ] := "#ffffff"; AAdd( aObjects, h ) h := BaseObject( "rectangulo", 4, 27, 22, 12 ); h[ "colorFondo" ] := "#2563eb"; h[ "bordeSuperior" ] := .T.; h[ "bordeDerecho" ] := .T.; h[ "bordeInferior" ] := .T.; h[ "bordeIzquierdo" ] := .T.; AAdd( aObjects, h ) h := BaseObject( "elipse", 30, 27, 18, 12 ); h[ "colorFondo" ] := "#ef4444"; h[ "bordeSuperior" ] := .T.; AAdd( aObjects, h ) h := BaseObject( "linea", 52, 27, 28, 12 ); h[ "orientacion" ] := "diagonal-up"; h[ "grosor" ] := 0.5; h[ "color" ] := "#000000"; AAdd( aObjects, h ) AAdd( aObjects, TextObject( 58, 5, 28, 8, "Girado", "#111111", 25 ) ) AAdd( aObjects, ImageObject( "public" + hb_ps() + "motor" + hb_ps() + "tests" + hb_ps() + "fixtures" + hb_ps() + "logo.png", "image/png", 4, 43 ) ) AAdd( aObjects, ImageObject( "public" + hb_ps() + "motor" + hb_ps() + "tests" + hb_ps() + "fixtures" + hb_ps() + "logo_dominus.jpg", "image/jpeg", 24, 43 ) ) h := BaseObject( "barcode", 48, 43, 44, 12 ); h[ "bits" ] := "1101001000010100011000100010110001000100011011101100011101011"; h[ "color" ] := "#000000"; AAdd( aObjects, h ) h := BaseObject( "barcode", 82, 27, 12, 12 ); h[ "matriz" ] := { "11111", "10001", "10101", "10001", "11111" }; h[ "margenModulos" ] := 1; h[ "color" ] := "#000000"; AAdd( aObjects, h ) RETURN { "nAncho" => 100.0, "nAlto" => 60.0, "paginas" => { { "objetos" => aObjects }, { "objetos" => {} } } } STATIC FUNCTION CropIR() LOCAL h := BaseObject( "rectangulo", 10, 12, 20, 10 ) h[ "colorFondo" ] := "#ffffff" RETURN { "nAncho" => 100.0, "nAlto" => 60.0, "paginas" => { { "objetos" => { h } } } } STATIC FUNCTION PartialIR() LOCAL h := BaseObject( "rectangulo", -5, 10, 15, 10 ) h[ "colorFondo" ] := "#000000" RETURN { "nAncho" => 100.0, "nAlto" => 60.0, "paginas" => { { "objetos" => { h } } } } STATIC FUNCTION LargeIR() RETURN { "nAncho" => 1000.0, "nAlto" => 1000.0, "paginas" => { { "objetos" => {} } } } STATIC FUNCTION BaseObject( cType, nX, nY, nWidth, nHeight ) RETURN { "tipo" => cType, "x" => nX, "y" => nY, "ancho" => nWidth, "alto" => nHeight, ; "colorFondo" => "transparent", "colorBorde" => "#000000", "grosorBorde" => 0.25, ; "bordeSuperior" => .F., "bordeDerecho" => .F., "bordeInferior" => .F., "bordeIzquierdo" => .F., ; "paddingSuperior" => 0, "paddingDerecho" => 0, "paddingInferior" => 0, "paddingIzquierdo" => 0, ; "opacidad" => 1, "rotacion" => 0 } STATIC FUNCTION TextObject( nX, nY, nWidth, nHeight, cText, cColor, nRotation ) LOCAL h := BaseObject( "texto", nX, nY, nWidth, nHeight ) h[ "texto" ] := cText; h[ "color" ] := cColor; h[ "fuente" ] := "Arial"; h[ "tamano" ] := 9 h[ "alineacionVertical" ] := "top"; h[ "ajusteLinea" ] := .F.; h[ "rotacion" ] := nRotation RETURN h STATIC FUNCTION ImageObject( cRelative, cMime, nX, nY ) LOCAL h := BaseObject( "imagen", nX, nY, 16, 12 ) LOCAL cFile := hb_DirBase() + ".." + hb_ps() + ".." + hb_ps() + ".." + hb_ps() + cRelative h[ "dataUri" ] := iif( File( cFile ), "data:" + cMime + ";base64," + hb_Base64Encode( hb_MemoRead( cFile ) ), "" ) h[ "mime" ] := cMime; h[ "ajuste" ] := "contain"; h[ "mantenerProporcion" ] := .T. RETURN h STATIC FUNCTION AssertTrue( cName, lValue, cDetail ) LOCAL cLine IF lValue cLine := "OK: " + cName QOut( cLine ) hb_MemoWrit( hb_DirBase() + "test_render_image_result.log", hb_MemoRead( hb_DirBase() + "test_render_image_result.log" ) + cLine + hb_eol() ) RETURN 0 ENDIF cLine := "ERROR: " + cName + iif( Empty( cDetail ), "", " -> " + cDetail ) QOut( cLine ) hb_MemoWrit( hb_DirBase() + "test_render_image_result.log", hb_MemoRead( hb_DirBase() + "test_render_image_result.log" ) + cLine + hb_eol() ) RETURN 1 STATIC FUNCTION AssertEqual( cName, uActual, uExpected ) RETURN AssertTrue( cName, uActual == uExpected, "actual=" + hb_ValToExp( uActual ) + ", esperado=" + hb_ValToExp( uExpected ) )