Skip to content

sale_product_image Images with transparent background, get their alpha channel converted to black #398

@Michiel-Celis

Description

@Michiel-Celis
Image

Images with transparent background, like from PNG, get their alpha channel (transparency) converted to black, resulting in weird prints.
This should be defaulted into white, and possibly given an option to change it.

After investigating i found Odoo converts PNG to WEBP, so alpha channel conversion is not so straight forward.
This can be solved with python package pillow.

Solution

requirements.txt

Pillow==11.3.0

utils/image_flatten.py

"""Image flatten utility: flatten PNG/WebP transparency to white using Pillow.

Moved from image_flatten_helper.py into utils namespace for clarity.
"""
from __future__ import annotations
import base64
import io

try:  # Pillow optional at runtime
    from PIL import Image  # type: ignore
except Exception:  # pragma: no cover
    Image = None  # type: ignore

def flatten_image_transparency_base64(image_data: str):
    """Flatten alpha to white for PNG/WebP base64 input. Returns base64 PNG or original.

    Placeholder fallback previously present is removed; if decode fails original is returned.
    """
    if not image_data:
        return image_data
    if not Image:
        return image_data
    try:
        raw = base64.b64decode(image_data)
    except Exception:
        return image_data
    try:
        with Image.open(io.BytesIO(raw)) as im:
            im.load()
            has_alpha = ('A' in im.getbands()) or ('transparency' in im.info)
            if has_alpha and im.mode not in ('RGBA', 'LA'):
                im = im.convert('RGBA')
            if has_alpha:
                alpha = im.split()[-1]
                bg = Image.new('RGBA', im.size, (255, 255, 255, 255))
                bg.paste(im, mask=alpha)
                out = bg.convert('RGB')
            else:
                out = im.convert('RGB') if im.mode not in ('RGB', 'L') else im
            buf = io.BytesIO()
            out.save(buf, format='PNG', optimize=True)
            return base64.b64encode(buf.getvalue()).decode('utf-8')
    except Exception:
        return image_data

models/sale_order_line.py

from odoo import api, fields, models
from ..utils import image_flatten as image_flatten_helper

try:  # Pillow optional; only used indirectly via helper
    from PIL import Image  # type: ignore
except Exception:  # pragma: no cover - environment without Pillow
    Image = None  # type: ignore


class SaleOrderLine(models.Model):
    """Inherits the model sale.order.line to add a field"""
    _inherit = 'sale.order.line'

    order_line_image = fields.Binary(string="Image",compute="_compute_order_line_image",help='Product Image with PNG transparency converted to white background')

    @api.depends('product_id.image_1920')
    def _compute_order_line_image(self):
        """Convert PNG alpha channel to white background using pure Python"""
        for line in self:
            if not line.product_id.image_1920:
                line.order_line_image = False
                continue
            try:
                converted_image = self._flatten_transparency(line.product_id.image_1920)
                line.order_line_image = converted_image or line.product_id.image_1920
            except Exception:
                # On any failure fall back silently to original image
                line.order_line_image = line.product_id.image_1920

    def _flatten_transparency(self, image_data):
        """Flatten transparency of PNG/WebP images using Pillow (via helper).

        Simplified: single helper call, minimal diagnostics. Returns original data on any failure.
        Placeholder fallback logic removed; if WebP decode fails upstream it just returns original.
        """
        if not image_data:
            return image_data
        try:
            return image_flatten_helper.flatten_image_transparency_base64(image_data)
        except Exception:  # pragma: no cover - defensive
            return image_data

models/res_config_settings.py

from odoo import fields, models


class ResConfigSettings(models.TransientModel):
    """Extend settings with toggles controlling sale order report images."""
    _inherit = 'res.config.settings'

    is_show_product_image_in_sale_report = fields.Boolean(
        string="Show Product Image",
        config_parameter='sale_product_image.is_show_product_image_in_sale_report',
        help='Display product images on the Sale Order report.')

    force_jpeg_sale_report_images = fields.Boolean(
        string="Force JPEG Conversion",
        config_parameter='sale_product_image.force_jpeg_sale_report_images',
        help='After flattening PNG/WebP transparency to white, convert to JPEG to reduce PDF size and avoid edge artifacts.')

manifest.py
add

    'external_dependencies': {
        'python': ['Pillow']
    }

Hope that helps.
Have a nice day !

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions