Skip to content

Commit

Permalink
2.0.0 - Material Design 3
Browse files Browse the repository at this point in the history
The `FitImage` widget is optimized according to the built-in `API`
of the `AsyncImage` widget.
  • Loading branch information
HeaTTheatR committed Dec 19, 2023
1 parent 3ff91b4 commit b885930
Showing 1 changed file with 22 additions and 110 deletions.
132 changes: 22 additions & 110 deletions kivymd/uix/fitimage/fitimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,6 @@
Components/FitImage
===================
Feature to automatically crop a `Kivy` image to fit your layout
Write by Benedikt Zwölfer
Referene - https://gist.github.com/benni12er/95a45eb168fc33a4fcd2d545af692dad
Example:
========
Expand Down Expand Up @@ -36,11 +30,11 @@
MDBoxLayout(
FitImage(
size_hint_y=.3,
size_hint_y=0.3,
source='images/img1.jpg',
),
FitImage(
size_hint_y=.7,
size_hint_y=0.7,
source='images/img2.jpg',
),
size_hint_y=None,
Expand Down Expand Up @@ -68,7 +62,7 @@
MDScreen:
MDCard:
radius: 36
radius: dp(36)
md_bg_color: "grey"
pos_hint: {"center_x": .5, "center_y": .5}
size_hint: .4, .8
Expand All @@ -77,7 +71,7 @@
source: "bg.jpg"
size_hint_y: .35
pos_hint: {"top": 1}
radius: 36, 36, 0, 0
radius: dp(36), dp(36), 0, 0
'''
Expand All @@ -93,6 +87,8 @@ def build(self):
.. code-block:: python
from kivy.metrics import dp
from kivymd.app import MDApp
from kivymd.uix.card import MDCard
from kivymd.uix.fitimage import FitImage
Expand All @@ -109,11 +105,11 @@ def build(self):
source="bg.jpg",
size_hint_y=0.35,
pos_hint={"top": 1},
radius=(36, 36, 0, 0),
radius=(dp(36), dp(36), 0, 0),
),
radius=36,
radius=dp(36),
md_bg_color="grey",
pos_hint={"center_x": .5, "center_y": .5},
pos_hint={"center_x": 0.5, "center_y": 0.5},
size_hint=(0.4, 0.8),
),
)
Expand All @@ -125,114 +121,30 @@ def build(self):

__all__ = ("FitImage",)

from kivy.clock import Clock
from kivy.graphics.context_instructions import Color
from kivy.graphics.vertex_instructions import Rectangle
from kivy.properties import BooleanProperty, ObjectProperty
from kivy.properties import OptionProperty
from kivy.uix.image import AsyncImage
from kivy.uix.widget import Widget

from kivymd.uix.behaviors import StencilBehavior
from kivymd.uix.boxlayout import MDBoxLayout


class FitImage(MDBoxLayout, StencilBehavior):
class FitImage(AsyncImage, StencilBehavior):
"""
Fit image class.
For more information, see in the
:class:`~kivymd.uix.boxlayout.MDLayout` and
:class:`~kivymd.uix.behaviors.StencilBehavior` classes documentation.
:class:`~kivy.uix.image.AsyncImage` and
:class:`~kivymd.uix.behaviors.stencil_behavior.StencilBehavior`
classes documentation.
"""

source = ObjectProperty()
fit_mode = OptionProperty(
"cover", options=["scale-down", "fill", "contain", "cover"]
)
"""
Filename/source of your image.
:attr:`source` is a :class:`~kivy.properties.StringProperty`
and defaults to None.
"""

mipmap = BooleanProperty(False)
"""
Indicate if you want OpenGL mipmapping to be applied to the texture.
Read :ref:`mipmap` for more information.
.. versionadded:: 1.0.0
Image will be stretched horizontally or vertically to fill the widget box,
**maintaining its aspect ratio**. If the image has a different aspect ratio
than the widget, then the image will be clipped to fit.
:attr:`mipmap` is a :class:`~kivy.properties.BooleanProperty`
and defaults to `False`.
:attr:`fit_mode` is a :class:`~kivy.properties.OptionProperty` and
defaults to `'cover'`.
"""

_container = ObjectProperty()

def __init__(self, **kwargs):
super().__init__(**kwargs)
Clock.schedule_once(self._late_init)

def _late_init(self, *args):
self._container = Container(self.source, self.mipmap)
self.bind(source=self._container.setter("source"))
self.add_widget(self._container)

def reload(self):
self._container.image.reload()


class Container(Widget):
source = ObjectProperty()
image = ObjectProperty()

def __init__(self, source, mipmap, **kwargs):
super().__init__(**kwargs)
self.image = AsyncImage(mipmap=mipmap)
self.loader_clock = Clock.schedule_interval(
self.adjust_size, self.image.anim_delay
)
self.image.bind(
on_load=lambda inst: (
self.adjust_size(),
self.loader_clock.cancel(),
)
)
self.source = source
self.bind(size=self.adjust_size, pos=self.adjust_size)

def on_source(self, instance, value):
if isinstance(value, str):
self.image.source = value
else:
self.image.texture = value
self.adjust_size()

def adjust_size(self, *args):
if not self.parent or not self.image.texture:
return

(par_x, par_y) = self.parent.size

if par_x == 0 or par_y == 0:
with self.canvas:
self.canvas.clear()
return

par_scale = par_x / par_y
(img_x, img_y) = self.image.texture.size
img_scale = img_x / img_y

if par_scale > img_scale:
(img_x_new, img_y_new) = (img_x, img_x / par_scale)
else:
(img_x_new, img_y_new) = (img_y * par_scale, img_y)

crop_pos_x = (img_x - img_x_new) / 2
crop_pos_y = (img_y - img_y_new) / 2

subtexture = self.image.texture.get_region(
crop_pos_x, crop_pos_y, img_x_new, img_y_new
)

with self.canvas:
self.canvas.clear()
Color(1, 1, 1)
Rectangle(texture=subtexture, pos=self.pos, size=(par_x, par_y))

0 comments on commit b885930

Please sign in to comment.