#include "hbclass.ch" CREATE CLASS TImageUtil METHOD New() CONSTRUCTOR METHOD Resolver( uValor, cTipoOrigen ) HIDDEN: METHOD DesdeContenido( cContenido, cRuta, cMime ) METHOD DetectarMime( cContenido, cRuta ) METHOD ResolverRuta( cRuta ) METHOD Error() METHOD DataUriImagen( cValor ) METHOD DecodeBase64Estricto( cBase64 ) METHOD EsMimeImagenValido( cMime ) METHOD ValorAString( uValor ) METHOD PathPublicSinPrefijo( cRuta ) METHOD NormalizarSeparadores( cRuta ) ENDCLASS METHOD New() CLASS TImageUtil RETURN Self METHOD Resolver( uValor, cTipoOrigen ) CLASS TImageUtil LOCAL cValor LOCAL cRuta LOCAL cContenido LOCAL hDataUri IF ! HB_ISSTRING( uValor ) .OR. uValor == "" RETURN ::Error() ENDIF cTipoOrigen := Lower( AllTrim( ::ValorAString( cTipoOrigen ) ) ) cValor := uValor IF cTipoOrigen != "datos" cRuta := ::ResolverRuta( cValor ) IF cRuta != "" cContenido := hb_MemoRead( cRuta ) RETURN ::DesdeContenido( cContenido, cRuta, "" ) ENDIF cContenido := hb_MemoRead( cValor ) IF ! Empty( cContenido ) RETURN ::DesdeContenido( cContenido, cValor, "" ) ENDIF cRuta := StrTran( cValor, "/", hb_ps() ) cContenido := hb_MemoRead( cRuta ) IF ! Empty( cContenido ) RETURN ::DesdeContenido( cContenido, cRuta, "" ) ENDIF ENDIF hDataUri := ::DataUriImagen( cValor ) IF hDataUri != NIL cContenido := ::DecodeBase64Estricto( hDataUri[ "base64" ] ) IF cContenido != NIL RETURN ::DesdeContenido( cContenido, "", Lower( hDataUri[ "mime" ] ) ) ENDIF ENDIF RETURN ::DesdeContenido( cValor, "", "" ) METHOD DesdeContenido( cContenido, cRuta, cMime ) CLASS TImageUtil cContenido := ::ValorAString( cContenido ) cRuta := ::ValorAString( cRuta ) cMime := ::ValorAString( cMime ) IF cMime == "" cMime := ::DetectarMime( cContenido, cRuta ) ENDIF IF cMime == "" RETURN ::Error() ENDIF RETURN { ; "mime" => cMime, ; "dataUri" => "data:" + cMime + ";base64," + hb_base64Encode( cContenido ), ; "error" => "" } METHOD DetectarMime( cContenido, cRuta ) CLASS TImageUtil LOCAL cRutaNormalizada := Lower( ::NormalizarSeparadores( ::ValorAString( cRuta ) ) ) LOCAL nPunto LOCAL cExtension := "" IF Left( cContenido, 8 ) == Chr( 137 ) + "PNG" + Chr( 13 ) + Chr( 10 ) + Chr( 26 ) + Chr( 10 ) RETURN "image/png" ENDIF IF Left( cContenido, 3 ) == Chr( 255 ) + Chr( 216 ) + Chr( 255 ) RETURN "image/jpeg" ENDIF IF Left( cContenido, 6 ) == "GIF87a" .OR. Left( cContenido, 6 ) == "GIF89a" RETURN "image/gif" ENDIF IF Len( cContenido ) >= 12 .AND. Left( cContenido, 4 ) == "RIFF" .AND. SubStr( cContenido, 9, 4 ) == "WEBP" RETURN "image/webp" ENDIF nPunto := RAt( ".", cRutaNormalizada ) IF nPunto > 0 cExtension := SubStr( cRutaNormalizada, nPunto + 1 ) ENDIF DO CASE CASE cExtension == "png" RETURN "image/png" CASE cExtension == "jpg" .OR. cExtension == "jpeg" RETURN "image/jpeg" CASE cExtension == "gif" RETURN "image/gif" CASE cExtension == "webp" RETURN "image/webp" ENDCASE RETURN "" METHOD ResolverRuta( cRuta ) CLASS TImageUtil LOCAL cNormalizada := AllTrim( ::ValorAString( cRuta ) ) LOCAL aRutas := {} LOCAL cPublicSinPrefijo LOCAL cCandidata LOCAL cCandidataLocal LOCAL cContenido IF cNormalizada == "" .OR. Chr( 0 ) $ cNormalizada .OR. Chr( 13 ) $ cNormalizada .OR. Chr( 10 ) $ cNormalizada RETURN "" ENDIF AAdd( aRutas, cNormalizada ) AAdd( aRutas, "public" + hb_ps() + cNormalizada ) cPublicSinPrefijo := ::PathPublicSinPrefijo( cNormalizada ) IF cPublicSinPrefijo != cNormalizada AAdd( aRutas, cPublicSinPrefijo ) AAdd( aRutas, "public" + hb_ps() + cPublicSinPrefijo ) ENDIF FOR EACH cCandidata IN aRutas cCandidataLocal := StrTran( cCandidata, "/", hb_ps() ) IF hb_FileExists( cCandidata ) RETURN cCandidata ENDIF IF hb_FileExists( cCandidataLocal ) RETURN cCandidataLocal ENDIF cContenido := hb_MemoRead( cCandidata ) IF ! Empty( cContenido ) RETURN cCandidata ENDIF cContenido := hb_MemoRead( cCandidataLocal ) IF ! Empty( cContenido ) RETURN cCandidataLocal ENDIF NEXT RETURN "" METHOD Error() CLASS TImageUtil RETURN { ; "mime" => "", ; "dataUri" => "", ; "error" => "Imagen no disponible" } METHOD DataUriImagen( cValor ) CLASS TImageUtil LOCAL nSeparador LOCAL cMime LOCAL cBase64 IF Lower( Left( cValor, 11 ) ) != "data:image/" RETURN NIL ENDIF nSeparador := At( ";BASE64,", Upper( cValor ) ) IF nSeparador <= 6 RETURN NIL ENDIF cMime := SubStr( cValor, 6, nSeparador - 6 ) IF ! ::EsMimeImagenValido( cMime ) RETURN NIL ENDIF cBase64 := SubStr( cValor, nSeparador + 8 ) IF cBase64 == "" RETURN NIL ENDIF RETURN { ; "mime" => cMime, ; "base64" => cBase64 } METHOD DecodeBase64Estricto( cBase64 ) CLASS TImageUtil LOCAL nIndex LOCAL cChar LOCAL nPadding := 0 LOCAL nLen IF cBase64 == "" .OR. Len( cBase64 ) % 4 == 1 RETURN NIL ENDIF nLen := Len( cBase64 ) FOR nIndex := 1 TO nLen cChar := SubStr( cBase64, nIndex, 1 ) IF cChar == "=" nPadding++ IF nIndex < Len( cBase64 ) - 1 RETURN NIL ENDIF ELSEIF !( cChar $ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" ) RETURN NIL ELSEIF nPadding > 0 RETURN NIL ENDIF NEXT IF nPadding > 2 RETURN NIL ENDIF RETURN hb_base64Decode( cBase64 ) METHOD EsMimeImagenValido( cMime ) CLASS TImageUtil LOCAL cNormalizado := Lower( ::ValorAString( cMime ) ) LOCAL nIndex LOCAL nChar LOCAL nLen IF Left( cNormalizado, 6 ) != "image/" .OR. Len( cNormalizado ) <= 6 RETURN .F. ENDIF nLen := Len( cNormalizado ) FOR nIndex := 7 TO nLen nChar := Asc( SubStr( cNormalizado, nIndex, 1 ) ) IF !( ( nChar >= 48 .AND. nChar <= 57 ) .OR. ; ( nChar >= 97 .AND. nChar <= 122 ) .OR. ; nChar == 43 .OR. nChar == 45 .OR. nChar == 46 ) RETURN .F. ENDIF NEXT RETURN .T. METHOD ValorAString( uValor ) CLASS TImageUtil IF uValor == NIL RETURN "" ENDIF IF HB_ISSTRING( uValor ) RETURN uValor ENDIF RETURN AllTrim( hb_ValToStr( uValor ) ) METHOD PathPublicSinPrefijo( cRuta ) CLASS TImageUtil LOCAL cNormalizada := ::NormalizarSeparadores( cRuta ) IF Lower( Left( cNormalizada, 7 ) ) == "public/" RETURN SubStr( cNormalizada, 8 ) ENDIF RETURN cRuta METHOD NormalizarSeparadores( cRuta ) CLASS TImageUtil RETURN StrTran( ::ValorAString( cRuta ), "\", "/" )