UNPKG

react-native-expo-cropper

Version:
144 lines (130 loc) 5.4 kB
import React, { forwardRef } from 'react'; import { View } from 'react-native'; import Svg, { Path, Defs, ClipPath, Image as SvgImage } from 'react-native-svg'; import { captureRef } from 'react-native-view-shot'; /** * ImageMaskProcessor - Module pour appliquer un masque alpha à une image * * NOTE: Ce module est actuellement désactivé par défaut (enableMask = false) * car captureRef peut se bloquer sur certaines configurations. * * Pour activer le masque, mettre enableMask = true dans ImageCropper.js * ATTENTION: Peut causer des blocages sur certaines configurations */ /** * Crée un chemin SVG à partir de points */ const createPathFromPoints = (points) => { if (points.length < 3) return ''; let path = `M ${points[0].x} ${points[0].y} `; for (let i = 1; i < points.length; i++) { path += `L ${points[i].x} ${points[i].y} `; } return path + 'Z'; }; /** * Composant MaskView - Vue avec image et masque SVG * Utilisé pour capturer l'image avec le masque appliqué */ export const MaskView = forwardRef(({ imageUri, points, width, height }, ref) => { const path = createPathFromPoints(points); // ✅ CORRECTION CRITIQUE : // Utiliser un VRAI clip SVG (ClipPath + SvgImage) au lieu d'un overlay noir. // Sinon captureRef peut capturer seulement l'overlay (noir/transparence) si l'Image RN n'est pas prête. return ( <View ref={ref} collapsable={false} style={{ width, height, overflow: 'hidden', backgroundColor: 'transparent', }} > <Svg width={width} height={height}> <Defs> <ClipPath id="cropClip"> <Path d={path} /> </ClipPath> </Defs> {/* Image rendue dans SVG + clip => extérieur transparent, intérieur = image */} <SvgImage width={width} height={height} href={{ uri: imageUri }} preserveAspectRatio="xMidYMid slice" clipPath="url(#cropClip)" /> </Svg> </View> ); }); MaskView.displayName = 'MaskView'; /** * Applique un masque alpha à une image en utilisant captureRef * * @param {string} imageUri - URI de l'image à masquer * @param {Array} points - Points définissant le masque (coordonnées normalisées) * @param {number} width - Largeur de l'image * @param {number} height - Hauteur de l'image * @param {React.Ref} maskViewRef - Référence à la vue MaskView * @returns {Promise<string>} URI de l'image masquée * * NOTE: Cette fonction utilise captureRef qui peut se bloquer. * Utiliser uniquement si enableMask = true dans ImageCropper.js */ // width/height = dimensions de sortie souhaitées EN PIXELS (pas dp) export const applyMaskToImage = async (imageUri, points, width, height, maskViewRef) => { try { if (!maskViewRef?.current) { console.warn("MaskView ref not available in applyMaskToImage"); return imageUri; } console.log("Applying mask with captureRef...", { imageUri, pointsCount: points.length, width, height, refAvailable: !!maskViewRef.current }); // ✅ CORRECTION : Capturer la vue avec le masque appliqué // captureRef supporte la transparence PNG pour préserver la forme exacte // Utiliser une qualité maximale et un format PNG pour préserver la transparence // Ajouter un petit délai supplémentaire pour s'assurer que la vue est complètement rendue await new Promise(resolve => setTimeout(resolve, 100)); // ⚠️ LIMITATION CRITIQUE : captureRef capture à la résolution de la vue React Native // La vue MaskView doit avoir les dimensions EXACTES de l'image croppée pour préserver la qualité // Si width/height sont les dimensions réelles de l'image croppée, la qualité sera préservée // Si width/height sont plus petits, la qualité sera réduite console.log("📸 Capturing mask view at requested output size (px):", { outputPx: { width, height }, imageUri, note: "✅ captureRef width/height forces output resolution; view can stay smaller in dp" }); // ✅ CORRECTION : Utiliser les dimensions réelles de l'image croppée // Si width/height correspondent aux dimensions réelles de croppedResult, la qualité sera maximale const maskedUri = await captureRef(maskViewRef.current, { format: 'png', // PNG supporte la transparence alpha quality: 1, // Qualité maximale (0-1) result: 'tmpfile', // Fichier temporaire // ✅ CORRECTION : forcer la résolution de sortie (pixels) // évite une capture 'écran' basée uniquement sur les dp de la vue (risque de downscale). width, height, // ✅ IMPORTANT : La résolution de capture = taille de la vue (width x height) // Si width/height = dimensions réelles de l'image croppée, qualité préservée // Si width/height < dimensions réelles, qualité réduite }); console.log("✅ Mask applied successfully:", { maskedUri, viewSize: `${width}x${height}`, qualityStatus: width >= 1000 && height >= 1000 ? "✓ High quality preserved" : "⚠️ Check if dimensions match original" }); return maskedUri; } catch (error) { console.error("Error applying mask:", error); // En cas d'erreur, retourner l'image originale (bounding box) return imageUri; } };