Skip to content

Commit

Permalink
Add Support for webp format (#6035)
Browse files Browse the repository at this point in the history
  • Loading branch information
vaniisgh authored Feb 18, 2024
1 parent f9d9a58 commit 313530f
Show file tree
Hide file tree
Showing 5 changed files with 165 additions and 11 deletions.
5 changes: 3 additions & 2 deletions examples/reference/panes/Image.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
"outputs": [],
"source": [
"jpg_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/jpeg_sample.jpeg')\n",
"png_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/png_sample.png')"
"png_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/png_sample.png')\n",
"webp_pane = pn.pane.Image('https://assets.holoviz.org/panel/samples/webp_sample.webp')"
]
},
{
Expand All @@ -58,7 +59,7 @@
},
"outputs": [],
"source": [
"pn.Column(jpg_pane, png_pane)"
"pn.Column(jpg_pane, png_pane, webp_pane)"
]
},
{
Expand Down
102 changes: 102 additions & 0 deletions examples/reference/panes/WebP.ipynb
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import panel as pn\n",
"\n",
"pn.extension()"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `WebP` pane embeds a `.web` image file in a panel if provided a local path, or will link to a remote image if provided a URL.\n",
"\n",
"#### Parameters:\n",
"\n",
"For details on other options for customizing the component see the [layout](../../how_to/layout/index.md) and [styling](../../how_to/styling/index.md) how-to guides.\n",
"\n",
"* **``alt_text``** (str, default=None): alt text to add to the image tag. The alt text is shown when a user cannot load or display the image. \n",
"* **``embed``** (boolean, default=False): If given a URL to an image this determines whether the image will be embedded as base64 or merely linked to.\n",
"* **``fixed_aspect``** (boolean, default=True): Whether the aspect ratio of the image should be forced to be equal.\n",
"* **``link_url``** (str, default=None): A link URL to make the image clickable and link to some other website.\n",
"* **``object``** (str or object): The PNG file to display. Can be a string pointing to a local or remote file, or an object with a ``_repr_png_`` method.\n",
"* **``style``** (dict): Dictionary specifying CSS styles\n",
"\n",
"___"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The `WebP` pane can be pointed at any local or remote `.webp` file. If given a URL starting with `http` or `https`, the `embed` parameter determines whether the image will be embedded or linked to:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"webp_pane = pn.pane.WebP('https://assets.holoviz.org/panel/samples/webp_sample.webp')\n",
"\n",
"webp_pane"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can scale the size of the image by setting a specific fixed `width` or `height`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"webp_pane.clone(width=400)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Alternatively we can scale the width and height using the `sizing_mode`:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"pn.pane.WebP(\n",
" 'https://assets.holoviz.org/panel/samples/webp_sample2.webp', sizing_mode='scale_width'\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Note that by default the aspect ratio of the image is fixed, and so there may be a gap beside or below the image even in responsive sizing modes. To override this behavior set `fixed_aspect=False` or provide fixed `width` and `height` values."
]
}
],
"metadata": {
"language_info": {
"name": "python",
"pygments_lexer": "ipython3"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
2 changes: 1 addition & 1 deletion panel/pane/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
from .equation import LaTeX # noqa
from .holoviews import HoloViews, Interactive # noqa
from .image import ( # noqa
GIF, ICO, JPG, PDF, PNG, SVG, Image,
GIF, ICO, JPG, PDF, PNG, SVG, Image, WebP,
)
from .ipywidget import IPyLeaflet, IPyWidget, Reacton # noqa
from .markup import ( # noqa
Expand Down
48 changes: 48 additions & 0 deletions panel/pane/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import asyncio
import base64
import struct

from io import BytesIO
from pathlib import PurePath
Expand Down Expand Up @@ -496,3 +497,50 @@ def _transform_object(self, obj: Any) -> Dict[str, Any]:
page = f'#page={self.start_page}' if getattr(self, 'start_page', None) else ''
html = f'<embed src="{obj}{page}" width={w!r} height={h!r} type="application/pdf">'
return dict(text=escape(html))

class WebP(ImageBase):
"""
The `WebP` pane embeds a .webp image file in a panel if
provided a local path, or will link to a remote image if provided
a URL.
Reference: https://developers.google.com/speed/webp/docs/riff_container
:Example:
>>> WebP(
... 'https://assets.holoviz.org/panel/samples/webp_sample.webp',
... alt_text='A nice tree',
... link_url='https://en.wikipedia.org/wiki/WebP',
... width=500,
... caption='A nice tree'
... )
"""

filetype: ClassVar[str] = 'webp'

_extensions: ClassVar[Tuple[str, ...]] = ('webp',)

@classmethod
def _imgshape(cls, data):
with BytesIO(data) as b:
b.read(12) # Skip RIFF header
chunk_header = b.read(4).decode('utf-8')
if chunk_header[:3] != 'VP8':
raise ValueError("Invalid WebP file")
wptype = chunk_header[3]
b.read(4)
if wptype == 'X':
b.read(4)
w = int.from_bytes(b.read(3), 'little') + 1
h = int.from_bytes(b.read(3), 'little') + 1
elif wptype == 'L':
b.read(1)
bits = struct.unpack("<I", b.read(4))[0]
w = (bits & 0x3FFF) + 1
h = ((bits >> 14) & 0x3FFF) + 1
elif wptype == ' ':
b.read(6)
w = int.from_bytes(b.read(2), 'little') + 1
h = int.from_bytes(b.read(2), 'little') + 1
return int(w), int(h)
19 changes: 11 additions & 8 deletions panel/tests/pane/test_image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@
from requests.exceptions import MissingSchema

from panel.pane import (
GIF, ICO, JPG, PDF, PNG, SVG,
GIF, ICO, JPG, PDF, PNG, SVG, WebP,
)
from panel.pane.markup import escape

JPG_FILE = 'https://assets.holoviz.org/panel/samples/jpg_sample.jpg'
JPEG_FILE = 'https://assets.holoviz.org/panel/samples/jpeg_sample.jpeg'
PNG_FILE = 'https://assets.holoviz.org/panel/samples/png_sample.png'
SVG_FILE = 'https://assets.holoviz.org/panel/samples/svg_sample.svg'

WEBP_FILE = 'https://assets.holoviz.org/panel/samples/webp_sample.webp'

def test_jpeg_applies():
assert JPG.applies(JPEG_FILE)
Expand Down Expand Up @@ -69,14 +69,17 @@ def test_svg_pane(document, comm):
b'AAAAAAAAAAAAAAAAFBv/EABkRAAEFAAAAAAAAAAAAAAAAAAEAAjFxsf/aAAwDAQ' + \
b'ACEQMRAD8AA0qs5HvTHQcJdsChioXSbOr/2Q==',
ico = b'AAABAAEAAgEAAAEAIAA0AAAAFgAAACgAAAACAAAAAgAAAAEAIAAAAAAACAAAAHQ' + \
b'SAAB0EgAAAAAAAAAAAAD//////////wAAAAA=')
b'SAAB0EgAAAAAAAAAAAAD//////////wAAAAA=',
webp= b'UklGRkIAAABXRUJQVlA4WAoAAAAQAAAAAQAAAAAAQUxQSAMAAAAAAAAAVlA4IBg' + \
b'AAAAwAQCdASoCAAEAAUAmJaQAA3AA/v02aAA='
)


def test_imgshape():
for t in [PNG, JPG, GIF, ICO]:
w,h = t._imgshape(b64decode(twopixel[t.name.lower()]))
assert w == 2
assert h == 1
@pytest.mark.parametrize('t', [PNG, JPG, GIF, ICO, WebP], ids=lambda t: t.name.lower())
def test_imgshape(t):
w, h = t._imgshape(b64decode(twopixel[t.name.lower()]))
assert w == 2
assert h == 1

def test_load_from_byteio():
"""Testing a loading a image from a ByteIo"""
Expand Down

0 comments on commit 313530f

Please sign in to comment.