Modify SmartObject PSD

Hello,
I purchased an Aspose Python PSD OEM license to perform a specific task, but I’m unable to achieve the expected result. I need support to resolve this.
When I try to replace the content of a smart object, the result is not the same as in Photoshop. Here are the two images: the first one was generated with Photoshop, and the second one was generated using the Aspose.PSD Python library.
Below is the code I used to replace the content of the smart object.

# Import des classes Aspose.PSD nécessaires
from aspose.psd import Image, License
from aspose.psd.fileformats.psd import PsdImage
from aspose.psd.fileformats.psd.layers.smartobjects import SmartObjectLayer
from aspose.psd.imageoptions import JpegOptions
from aspose.psd.imageloadoptions import PsdLoadOptions
from aspose.pycore import cast

# ✅ 1. Appliquer la licence Aspose (si vous avez un fichier .lic)
try:
    license = License()
    license.set_license("Aspose.PSD.Python.NET.lic")
    print("Licence appliquée avec succès.")
except Exception as e:
    print(f"Erreur lors de l'application de la licence : {e}")
    print("Continuation en mode d'évaluation...")

# ✅ 2. Charger le fichier PSD
load_options = PsdLoadOptions()
load_options.load_effects_resource = True

with Image.load("A.psd", load_options) as image:
    # Cast vers PsdImage pour accéder aux layers
    psd_image = cast(PsdImage, image)
    found = False

    # ✅ 3. Rechercher le Smart Object nommé "GC-COEUR"
    print("Layers disponibles dans le PSD :")
    print(f"Nombre de layers: {len(psd_image.layers)}")
    
    for i, layer in enumerate(psd_image.layers):
        layer_type = type(layer).__name__
        layer_name = getattr(layer, 'display_name', 'N/A')
        print(f"  Layer {i}: {layer_name} (Type: {layer_type})")
        
        if layer_name == "GC-COEUR":
            print(f"Analyse détaillée du layer GC-COEUR:")
            print(f"  - Type direct: {type(layer)}")
            print(f"  - Attributs: {[attr for attr in dir(layer) if 'smart' in attr.lower()]}")
            
            try:
                smart_layer = cast(SmartObjectLayer, layer)
                print(f"  - Cast SmartObjectLayer réussi!")
                print("Smart Object trouvé : GC-COEUR — édition du contenu interne...")
                try:
                    print("Chargement du contenu existant du Smart Object...")
                    existing_content = smart_layer.load_contents(None)
                    print(f"Contenu existant: {type(existing_content)}, Taille: {existing_content.width}x{existing_content.height}")
                    
                    print("Création d'un canvas basé sur le contenu existant...")
                    from aspose.psd import Graphics, Rectangle, Color
                    from aspose.psd.fileformats.psd import PsdImage as NewPsdImage
                    
                    try:
                        canvas = NewPsdImage(existing_content.width, existing_content.height)
                        graphics = Graphics(canvas)
                        
                        print("Préservation du contenu existant...")
                        dest_rect_base = Rectangle(0, 0, existing_content.width, existing_content.height)
                        graphics.draw_image(existing_content, dest_rect_base)
                        
                        print("Ajout du logo par-dessus le contenu existant...")
                        with Image.load("logo.png") as logo:
                            print(f"Logo original: {logo.width}x{logo.height}")
                            
                            canvas_width = existing_content.width
                            canvas_height = existing_content.height
                            print(f"Canvas: {canvas_width}x{canvas_height}")
                            
                            max_width = int(canvas_width * 0.5)
                            max_height = int(canvas_height * 0.5)
                            
                            logo_aspect = logo.width / logo.height
                            
                            if logo.width > max_width or logo.height > max_height:
                                if max_width / logo_aspect <= max_height:
                                    new_width = max_width
                                    new_height = int(max_width / logo_aspect)
                                else:
                                    new_height = max_height
                                    new_width = int(max_height * logo_aspect)
                            else:
                                new_width = logo.width
                                new_height = logo.height
                            
                            print(f"Logo redimensionné: {new_width}x{new_height}")
                            
                            # Position centrée
                            x = (canvas_width - new_width) // 2
                            y = (canvas_height - new_height) // 2
                            
                            print(f"Position du logo: x={x}, y={y}")
                            
                            # Dessiner le logo PAR-DESSUS le contenu existant
                            logo_rect = Rectangle(x, y, new_width, new_height)
                            graphics.draw_image(logo, logo_rect)
                            
                            print("Logo ajouté par-dessus le contenu existant.")
                            
                            # Étape 4: Remplacer le contenu du Smart Object avec la composition
                            print("Mise à jour du Smart Object avec la composition (contenu + logo)...")
                            smart_layer.replace_contents(canvas)
                            
                            # Étape 5: Mettre à jour pour préserver les transformations
                            print("Application des transformations...")
                            try:
                                smart_layer.update_all_modified_content()
                                print("Transformations appliquées avec succès.")
                            except Exception as update_ex:
                                print(f"Avertissement: {update_ex}")
                            
                            print("Succès! Logo ajouté avec préservation du mockup original et des transformations.")
                    finally:
                        # Nettoyer les ressources
                        if 'graphics' in locals():
                            try:
                                graphics.dispose()
                            except:
                                pass
                        if 'canvas' in locals():
                            try:
                                canvas.dispose()
                            except:
                                pass
                        if 'existing_content' in locals():
                            try:
                                existing_content.dispose()
                            except:
                                pass
                    
                    found = True
                    break
                except Exception as ex:
                    print(f"Erreur lors du remplacement : {ex}")
                    print("Tentative avec le chemin de fichier...")
                    try:
                        smart_layer.replace_contents("logo.png")
                        print("Remplacement avec chemin de fichier réussi.")
                        found = True
                        break
                    except Exception as ex2:
                        print(f"Erreur lors du remplacement avec chemin : {ex2}")
            except Exception as cast_ex:
                print(f"  - Cast SmartObjectLayer échoué: {cast_ex}")
                print(f"  - Ce n'est pas un Smart Object")
        
        if isinstance(layer, SmartObjectLayer):
            print(f"    -> Smart Object détecté : {layer.display_name}")
            if layer.display_name.lower() == "gc-coeur":
                print("Smart Object trouvé : GC-COEUR — remplacement en cours...")
                try:
                    layer.replace_contents("logo.png")
                    print("Remplacement réussi.")
                except Exception as ex:
                    print(f"Erreur lors du remplacement : {ex}")
                found = True
                break

    if not found:
        print("Smart Object 'GC-COEUR' introuvable.")

    # ✅ 4. Enregistrer le PSD modifié
    psd_image.save("A_output.psd")
    print("PSD sauvegardé sous 'A_output.psd'.")

    # ✅ 5. Exporter en JPG
    jpg_options = JpegOptions()
    jpg_options.quality = 90  # qualité JPEG à 90%
    psd_image.save("A_output.jpg", jpg_options)
    print("JPG exporté sous 'A_output.jpg'.")

Photoshop-render.jpg (146,4 Ko)
AsPose-render.jpg (147,4 Ko)

@Damien_Chazal

To modify the content of a Smart Object in a PSD file using Aspose.PSD for Python, it’s crucial to ensure that the updates are made correctly to match the expected results from Photoshop. Here’s a breakdown of your code and some potential adjustments you might consider to improve the output.

Key Steps to Update Smart Objects

  1. Load the PSD File: Ensure you are loading the PSD file correctly with the necessary options to access the effects and smart objects.

  2. Find the Smart Object Layer: Verify that you are correctly identifying the Smart Object layer you want to modify.

  3. Load Existing Content: When loading the content of the Smart Object, ensure you handle it appropriately to maintain its original properties.

  4. Manipulate Content: After loading the existing content, you can manipulate it (e.g., adding images, applying transformations).

  5. Replace Contents: Use the replace_contents method to update the Smart Object layer with the manipulated content.

  6. Update Modifications: It is essential to call the method to update the modified content to ensure that any transformations or changes are applied correctly.

Example Code Adjustment

Here’s a refined version of your code snippet focused on these points:

from aspose.psd import Image, License
from aspose.psd.fileformats.psd import PsdImage
from aspose.psd.fileformats.psd.layers.smartobjects import SmartObjectLayer
from aspose.psd.imageoptions import JpegOptions
from aspose.psd.imageloadoptions import PsdLoadOptions
from aspose.pycore import cast

# Load and apply the license
license = License()
license.set_license("Aspose.PSD.Python.NET.lic")

# Load the PSD file
load_options = PsdLoadOptions()
load_options.load_effects_resource = True

with Image.load("A.psd", load_options) as image:
    psd_image = cast(PsdImage, image)
    for layer in psd_image.layers:
        if isinstance(layer, SmartObjectLayer) and layer.display_name == "GC-COEUR":
            # Load existing content
            existing_content = layer.load_contents(None)
            # Manipulate content (e.g., add new logo)
            # Create a graphics object to draw on the existing content
            canvas = PsdImage(existing_content.width, existing_content.height)
            graphics = Graphics(canvas)
            graphics.draw_image(existing_content, Rectangle(0, 0, existing_content.width, existing_content.height))
            # Add logo and other manipulations here
            layer.replace_contents(canvas)
            layer.update_all_modified_content()
            break

# Save the modified PSD
psd_image.save("A_output.psd")

Common Issues to Consider

  • Transformations: Ensure that any transformations done in the code replicate what you do in Photoshop. Sometimes scaling or positioning issues can arise if the sizes or aspect ratios are not handled correctly.
  • Contents Update: Always call the appropriate update methods after making changes to ensure that all modifications are correctly reflected.
  • Exporting: When exporting, ensure that the settings match your desired output quality and format.

Conclusion

Make sure to follow these steps closely, and adjust your image manipulation logic according to the specific requirements of your design. If necessary, debug by checking the dimensions and properties of the images being manipulated to ensure they align with your expectations from Photoshop.

For more details on the SmartObjectLayer class and its methods, you can refer to the official Aspose documentation on Smart Object manipulation.

If you continue to experience issues, consider sharing specific details about the discrepancies in the output images, as this can help in diagnosing any specific problems further.

Sources:
[1]: Smart Object Update and Export using Aspose.PSD for Python
[2]: SmartObjectLayer Class | Aspose API References

Hello,
Thank you for the automated response, but it doesn’t work. Would it be possible to get more intelligent human assistance, please? Thank you :blush:

@Damien_Chazal
We need to investigate this. As a quick answer, please check resolution of the the image you want to use as a replacement.
Please find the second method for this operation:
https://reference.aspose.com/psd/python-net/aspose.psd.fileformats.psd.layers.smartobjects/smartobjectlayer/#replace_contents_image_resolution_41

If the resoulution of source and new images differs, this can help.

Also, at this moment team works on the fixes of image size after the replace_contents, but this fix can be applicable only for specific case. Could you please provide input files to help us check if it’s a resolution issue, or the new one. I’ve attached this issue to your topic.

We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): PSDNET-2474,PSDPYTHON-194

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.

Hello @Dmitriy.Sorokin
Thanks for answer.

So I try the second method but doesn’t works as you can see attached file (render is not good with aspose).
Aspose-Render.jpg (188,2 Ko)
PhotoShop-Render.jpg (48,5 Ko)

This is code I use :

from aspose.psd import Image, License, ResolutionSetting, Graphics, Rectangle
from aspose.psd.fileformats.psd import PsdImage
from aspose.psd.fileformats.psd.layers import Layer
from aspose.psd.fileformats.psd.layers.smartobjects import SmartObjectLayer
from aspose.psd.imageoptions import PngOptions
from aspose.psd.imageloadoptions import PsdLoadOptions
from aspose.pycore import cast
import os

# ✅ 1. Appliquer la licence Aspose (si vous avez un fichier .lic)
try:
    license = License()
    license.set_license("Aspose.PSD.Python.NET.lic")
    print("Licence appliquée avec succès.")
except Exception as e:
    print(f"Erreur lors de l'application de la licence : {e}")
    print("Continuation en mode d'évaluation...")

def get_image_resolution(image_path):
    """
    Obtient la résolution (DPI) d'une image
    """
    try:
        with Image.load(image_path) as img:
            h_res = img.horizontal_resolution if hasattr(img, 'horizontal_resolution') else 72.0
            v_res = img.vertical_resolution if hasattr(img, 'vertical_resolution') else 72.0
            print(f"  Résolution de {image_path}: {h_res} x {v_res} DPI")
            return h_res, v_res
    except Exception as e:
        print(f"  Impossible d'obtenir la résolution, utilisation de 72 DPI par défaut: {e}")
        return 72.0, 72.0

def edit_smartobject_content(mockup_path, layer_name, replacement_image, output_path):
    """
    Édite le contenu d'un SmartObject en manipulant directement son contenu
    au lieu de le remplacer complètement (edit vs replace)
    """
    try:
        # Vérifier que les fichiers existent
        if not os.path.exists(mockup_path):
            raise FileNotFoundError(f"Le fichier mockup '{mockup_path}' n'existe pas")
        
        if not os.path.exists(replacement_image):
            raise FileNotFoundError(f"L'image de remplacement '{replacement_image}' n'existe pas")
        
        print(f"Chargement du mockup: {mockup_path}")
        
        # Options de chargement avec support des effets
        load_options = PsdLoadOptions()
        load_options.load_effects_resource = True
        load_options.allow_warp_repaint = True
        
        # Charger le fichier PSD
        with Image.load(mockup_path, load_options) as image:
            psd_image = cast(PsdImage, image)
            
            print(f"Mockup chargé. Nombre de layers: {len(psd_image.layers)}")
            
            # Obtenir la résolution de l'image de remplacement
            print("\nAnalyse de la résolution de l'image de remplacement...")
            new_h_res, new_v_res = get_image_resolution(replacement_image)
            
            # Trouver le layer cible
            target_layer = None
            layer_index = -1
            
            for i, layer in enumerate(psd_image.layers):
                print(f"Layer {i}: {layer.name} - Type: {type(layer).__name__}")
                
                if layer.name == layer_name:
                    target_layer = layer
                    layer_index = i
                    print(f"\n✓ Layer '{layer_name}' trouvé à l'index {i}!")
                    break
            
            if target_layer is None:
                print(f"\n✗ Layer '{layer_name}' non trouvé")
                return False
            
            # === MÉTHODE EDIT CONTENT (au lieu de REPLACE CONTENT) ===
            print("\n=== Édition du contenu SmartObject (Edit Content) ===")
            
            try:
                # Caster en SmartObjectLayer
                smart_layer = cast(SmartObjectLayer, target_layer)
                print("✓ Cast en SmartObjectLayer réussi!")
                
                # ÉTAPE 1: Charger le contenu existant du SmartObject
                print("\n1. Chargement du contenu existant...")
                with smart_layer.load_contents(None) as existing_content:
                    print(f"  - Contenu existant: {existing_content.width} x {existing_content.height}")
                    
                    # ÉTAPE 2: Créer un nouveau canvas avec les dimensions du contenu existant
                    print("\n2. Création d'un nouveau canvas...")
                    canvas = PsdImage(existing_content.width, existing_content.height)
                    
                    # Définir la résolution du canvas
                    canvas.horizontal_resolution = new_h_res
                    canvas.vertical_resolution = new_v_res
                    print(f"  - Canvas créé: {canvas.width} x {canvas.height} à {new_h_res} DPI")
                    
                    # ÉTAPE 3: Utiliser Graphics pour composer le nouveau contenu
                    print("\n3. Composition du nouveau contenu...")
                    graphics = Graphics(canvas)
                    
                    # Option A: Conserver le contenu existant en arrière-plan
                    print("  - Dessin du contenu existant en arrière-plan...")
                    graphics.draw_image(existing_content, Rectangle(0, 0, canvas.width, canvas.height))
                    
                    # Option B: Ajouter la nouvelle image par-dessus
                    print(f"  - Ajout de la nouvelle image: {replacement_image}")
                    with Image.load(replacement_image) as new_img:
                        # Calculer les dimensions pour centrer l'image
                        scale_factor = min(canvas.width / new_img.width, canvas.height / new_img.height)
                        new_width = int(new_img.width * scale_factor)
                        new_height = int(new_img.height * scale_factor)
                        
                        # Centrer l'image
                        x = (canvas.width - new_width) // 2
                        y = (canvas.height - new_height) // 2
                        
                        print(f"    - Dimensions: {new_width} x {new_height}")
                        print(f"    - Position: ({x}, {y})")
                        
                        # Dessiner la nouvelle image centrée
                        graphics.draw_image(new_img, Rectangle(x, y, new_width, new_height))
                    
                    # ÉTAPE 4: Remplacer le contenu avec le canvas composé
                    print("\n4. Remplacement du contenu du SmartObject...")
                    
                    # Créer ResolutionSetting si nécessaire
                    resolution = ResolutionSetting(new_h_res, new_v_res)
                    
                    # Remplacer avec le nouveau canvas
                    smart_layer.replace_contents(canvas, resolution)
                    
                    # Mise à jour du contenu modifié
                    smart_layer.update_modified_content()
                    print("✓ Contenu édité et mis à jour avec succès!")
                
                # ALTERNATIVE: Accès direct à la propriété contents (si disponible)
                print("\n=== Alternative: Accès direct aux contents ===")
                try:
                    # Tenter d'accéder directement à la propriété contents
                    if hasattr(smart_layer, 'contents'):
                        print("✓ Propriété 'contents' disponible")
                        
                        # Charger la nouvelle image comme données binaires
                        with open(replacement_image, 'rb') as f:
                            new_image_data = f.read()
                        
                        # Remplacer directement le contenu (méthode alternative)
                        # Note: Cette approche est plus directe mais moins flexible
                        print("  - Remplacement direct des données du contenu...")
                        # smart_layer.contents = new_image_data  # Décommenté si cette propriété existe
                        
                    else:
                        print("ℹ Propriété 'contents' non disponible pour accès direct")
                        
                except Exception as e:
                    print(f"  Accès direct aux contents non possible: {e}")
                
            except Exception as cast_error:
                print(f"✗ Cast en SmartObjectLayer échoué: {cast_error}")
                
                # Fallback: Utiliser replace_contents classique si edit content échoue
                print("\n→ Fallback vers replace_contents...")
                if hasattr(target_layer, 'replace_contents'):
                    try:
                        resolution = ResolutionSetting(new_h_res, new_v_res)
                        target_layer.replace_contents(replacement_image, resolution)
                        if hasattr(target_layer, 'update_modified_content'):
                            target_layer.update_modified_content()
                        print("✓ Fallback réussi avec replace_contents!")
                    except Exception as e:
                        print(f"✗ Fallback échoué: {e}")
                        return False
            
            # Sauvegarder le fichier
            print(f"\nSauvegarde du fichier: {output_path}")
            psd_image.save(output_path)
            
            # Exporter en PNG pour prévisualisation
            png_output = output_path.replace('.psd', '_edit_preview.png')
            print(f"Export PNG: {png_output}")
            png_options = PngOptions()
            psd_image.save(png_output, png_options)
            
            return True
            
    except Exception as e:
        print(f"\n✗ Erreur: {str(e)}")
        import traceback
        traceback.print_exc()
        return False

def edit_smartobject_blend_mode(mockup_path, layer_name, replacement_image, output_path, preserve_background=True):
    """
    Version avancée avec modes de fusion et options de composition
    """
    try:
        print(f"\n=== Édition avancée avec modes de fusion ===")
        
        with Image.load(mockup_path, PsdLoadOptions()) as image:
            psd_image = cast(PsdImage, image)
            
            # Trouver le SmartObject
            for layer in psd_image.layers:
                if layer.name == layer_name:
                    smart_layer = cast(SmartObjectLayer, layer)
                    
                    with smart_layer.load_contents(None) as existing_content:
                        # Créer un canvas pour la composition
                        canvas = PsdImage(existing_content.width, existing_content.height)
                        graphics = Graphics(canvas)
                        
                        if preserve_background:
                            # Conserver l'arrière-plan original
                            graphics.draw_image(existing_content, Rectangle(0, 0, canvas.width, canvas.height))
                        
                        # Charger et traiter la nouvelle image
                        with Image.load(replacement_image) as new_img:
                            # Redimensionner pour s'adapter
                            scale = min(canvas.width / new_img.width, canvas.height / new_img.height) * 0.8  # 80% de la taille
                            new_width = int(new_img.width * scale)
                            new_height = int(new_img.height * scale)
                            
                            # Positionner au centre
                            x = (canvas.width - new_width) // 2
                            y = (canvas.height - new_height) // 2
                            
                            # Dessiner avec transparence/fusion
                            graphics.draw_image(new_img, Rectangle(x, y, new_width, new_height))
                        
                        # Remplacer le contenu
                        smart_layer.replace_contents(canvas)
                        smart_layer.update_modified_content()
                        
                        print("✓ Édition avec mode de fusion terminée!")
                        break
            
            # Sauvegarder
            psd_image.save(output_path)
            
            # Export PNG
            png_output = output_path.replace('.psd', '_blend_preview.png')
            psd_image.save(png_output, PngOptions())
            
            return True
            
    except Exception as e:
        print(f"Erreur lors de l'édition avec modes de fusion: {e}")
        return False

def compare_edit_vs_replace(mockup_path, layer_name, replacement_image):
    """
    Compare les méthodes Edit Content vs Replace Content
    """
    print("\n" + "="*60)
    print("COMPARAISON: EDIT CONTENT vs REPLACE CONTENT")
    print("="*60)
    
    # Test 1: Replace Content (méthode classique)
    print("\n1. Test REPLACE CONTENT (méthode classique)...")
    success_replace = replace_smartobject_with_resolution(
        mockup_path, layer_name, replacement_image, "output_replace.psd"
    )
    
    # Test 2: Edit Content (nouvelle méthode)
    print("\n2. Test EDIT CONTENT (nouvelle méthode)...")
    success_edit = edit_smartobject_content(
        mockup_path, layer_name, replacement_image, "output_edit.psd"
    )
    
    # Test 3: Edit Content avec modes de fusion
    print("\n3. Test EDIT CONTENT avec modes de fusion...")
    success_blend = edit_smartobject_blend_mode(
        mockup_path, layer_name, replacement_image, "output_blend.psd", preserve_background=True
    )
    
    # Résumé
    print("\n" + "="*60)
    print("RÉSULTATS:")
    print(f"  Replace Content: {'✓ Réussi' if success_replace else '✗ Échoué'}")
    print(f"  Edit Content:    {'✓ Réussi' if success_edit else '✗ Échoué'}")
    print(f"  Edit Blend:      {'✓ Réussi' if success_blend else '✗ Échoué'}")
    print("="*60)
    
    return success_replace, success_edit, success_blend

# Fonction de référence (depuis test_script3.py)
def replace_smartobject_with_resolution(mockup_path, layer_name, replacement_image, output_path):
    """
    Méthode de référence replace_contents (depuis test_script3.py)
    """
    try:
        load_options = PsdLoadOptions()
        load_options.load_effects_resource = True
        load_options.allow_warp_repaint = True
        
        with Image.load(mockup_path, load_options) as image:
            psd_image = cast(PsdImage, image)
            
            for layer in psd_image.layers:
                if layer.name == layer_name:
                    try:
                        smart_layer = cast(SmartObjectLayer, layer)
                        new_h_res, new_v_res = get_image_resolution(replacement_image)
                        resolution = ResolutionSetting(new_h_res, new_v_res)
                        smart_layer.replace_contents(replacement_image, resolution)
                        smart_layer.update_modified_content()
                        break
                    except Exception as e:
                        print(f"Erreur replace_contents: {e}")
                        return False
            
            psd_image.save(output_path)
            return True
            
    except Exception as e:
        print(f"Erreur replace method: {e}")
        return False

# Programme principal
def main():
    mockup_file = "B.psd"
    logo_file = "logo.png"
    layer_to_replace = "GC-LARGE"
    
    print("=== TEST SCRIPT 4: EDIT CONTENT vs REPLACE CONTENT ===\n")
    
    # Vérifier les fichiers d'entrée
    if not os.path.exists(mockup_file):
        print(f"✗ Fichier mockup manquant: {mockup_file}")
        return
    
    if not os.path.exists(logo_file):
        print(f"✗ Fichier logo manquant: {logo_file}")
        return
    
    print("✓ Fichiers d'entrée trouvés")
    
    # Menu interactif
    print("\nChoisissez le test à effectuer:")
    print("1. Edit Content (nouvelle méthode)")
    print("2. Edit Content avec modes de fusion")
    print("3. Comparaison complète (Edit vs Replace)")
    print("4. Tous les tests")
    
    choice = input("Votre choix (1-4): ").strip()
    
    if choice == "1":
        success = edit_smartobject_content(mockup_file, layer_to_replace, logo_file, "C_edit_output.psd")
        print(f"\n{'✓ Succès!' if success else '✗ Échec'}")
        
    elif choice == "2":
        success = edit_smartobject_blend_mode(mockup_file, layer_to_replace, logo_file, "C_blend_output.psd")
        print(f"\n{'✓ Succès!' if success else '✗ Échec'}")
        
    elif choice == "3":
        compare_edit_vs_replace(mockup_file, layer_to_replace, logo_file)
        
    elif choice == "4":
        print("\n=== EXÉCUTION DE TOUS LES TESTS ===")
        
        # Test Edit Content
        print("\n--- Test 1: Edit Content ---")
        edit_smartobject_content(mockup_file, layer_to_replace, logo_file, "C_edit_output.psd")
        
        # Test Edit Blend
        print("\n--- Test 2: Edit Content avec fusion ---")
        edit_smartobject_blend_mode(mockup_file, layer_to_replace, logo_file, "C_blend_output.psd")
        
        # Test Comparaison
        print("\n--- Test 3: Comparaison complète ---")
        compare_edit_vs_replace(mockup_file, layer_to_replace, logo_file)
        
        print("\n✓ Tous les tests terminés!")
        print("\nFichiers générés:")
        print("  - C_edit_output.psd (+ PNG preview)")
        print("  - C_blend_output.psd (+ PNG preview)")
        print("  - output_replace.psd (+ PNG preview)")
        print("  - output_edit.psd (+ PNG preview)")
        print("  - output_blend.psd (+ PNG preview)")
    
    else:
        print("Choix invalide!")
        return
    
    print("\n💡 Différences entre Edit et Replace:")
    print("  • REPLACE: Remplace complètement le contenu")
    print("  • EDIT: Compose/édite le contenu existant")
    print("  • BLEND: Combine l'ancien et le nouveau contenu")

if __name__ == "__main__":
    main()

I can send you mockup but 100Mo size too much for forum ^^
I have buy a Developer OEM license so I think I can obtain Paid Support Services but it doesn’t work when I try to create a new ticket :confused:

Thanks for your attention

@Damien_Chazal paid support services and the licenses are the different products.

If it’s possible, please upload original zipped file to the some cloud storage to give us ability to download it. Please note, if the attached images can not be downloaded by 3rd party users, link can be used by them to obtain your file, so you can send me url via direct message.

Code looks correct. The quick workaround is to change parameters of image for replacement. The result of rendering looks like the the bug that is in the work now, it’s planned on 25.7.

Input file will help us to fix your issue.

@Dmitriy.Sorokin this is the link to download all files : Aspose Test with PSD Mockup

You will find the mockup, the code, the logo and you can see difference between photoshop render and aspose render

Thanks !

@Damien_Chazal
I’ll write you after investigation, or you’ll receive notification whe the issue will be fixed.

We have opened the following new ticket(s) in our internal issue tracking system and will deliver their fixes according to the terms mentioned in Free Support Policies.

Issue ID(s): PSDNET-2484,PSDPYTHON-195

You can obtain Paid Support Services if you need support on a priority basis, along with the direct access to our Paid Support management team.

Hello @Dmitriy.Sorokin
Have you found a lead regarding the issue mentioned ? Do you have an idea of how much time it might take to resolve it ?
I purchased the license for this specific use, and if no solution is possible, I will need a refund.
Thank you in advance.
Damien

@Damien_Chazal sorry for the long investigation. The Team is finishing fixing the issue with incorrect size for 25.6. I’ll write you tomorrow, I’ll need to check if this fix is solving your issue, if so, fix will be in 2 weeks.

@Damien_Chazal we’ve checked current status of rendering. So, please check the example. It has artifacts, but the addiotional fix is in work that will improve quality and speed of warp rendering. Release 25.7 is in work, so in the end of the week will be release .NET version and 2 days after will be released Python Version.
040404_upd (1).jpg (46.7 KB)

Thanks @Dmitriy.Sorokin for the update. I’ve checked the example and noticed the artifacts as mentioned. Looking forward to the additional fix. Appreciate the timeline as well :wink: Thanks.

Hello @Dmitriy.Sorokin
Hope you’re fine ^^
Just to know if it’s fixed ?
Thanks

@Damien_Chazal the issue PSDNET-2474 and related PSDPYTHON-194 is in progress now. Current version 25.7 that was released on Monday has the artifacts, but processing SmartObjects much better. aspose-psd · PyPI

@Dmitriy.Sorokin Thank you for your feedback. Do you plan to fully fix the issue in the next release ?

@Damien_Chazal I can’t guarantee pixel-perfect result, but the team works very seriosly on the quality of SmartObject transformation.