Various improvements to image file handling:

Moved MS DIB parser out of ICO file reader and made it available for
artwork and layout images.

Added more efficient I/O and better error checking for JPEG file loading
(MAME will no longer exit immediately on a bad JPEG file).

Made caller responsible for opening files for loading images, to avoid
decompressing images used in ZIP/7z artwork multiple times.

Added support for JPEG and Windows DIB to picture_image_device.

Added support for SVG image files in external artwork.

Added support for using I/O port value for animation state and masking
animation state values.

Made bounds elements more flexible in layouts.

Reworked headers to reduce dependencies.

Updated layout file format documentation.
This commit is contained in:
Vas Crabb 2020-10-08 02:04:31 +11:00
parent 4b0903bfdb
commit b90fd35515
42 changed files with 2204 additions and 1325 deletions

View File

@ -64,7 +64,7 @@ in the rightward and downward directions. The origin (0,0) has no particular
significance, and you may freely use negative coordinates in layouts.
Coordinates are supplied as floating-point numbers.
MAME assumes that view coordinates have the same aspect ratio as pixel on the
MAME assumes that view coordinates have the same aspect ratio as pixels on the
output device (host screen or window). Assuming square pixels and no rotation,
this means equal distances in X and Y axes correspond to equal horizontal and
vertical distances in the rendered output.
@ -73,20 +73,30 @@ Views, groups and elements all have their own internal coordinate systems. When
an element or group is referenced from a view or another group, its coordinates
are scaled as necessary to fit the specified bounds.
Objects are positioned and sized using ``bounds`` elements. A bounds element
may specify the position of the top left corner and the size using ``x``, ``y``,
``width`` and ``height`` attributes, or it may specify the coordinates of the
edges with the ``left``, ``top``, ``right`` and ``bottom`` attributes. These
two ``bounds`` elements are equivalent::
Objects are positioned and sized using ``bounds`` elements. The horizontal
position and size may be specified in three ways: left edge and width using
``x`` and ``width`` attributes, horizontal centre and width using ``xc`` and
``width`` attributes, or left and right edges using ``left`` and ``right``
attributes. Similarly, the vertical position and size may be specified in terms
of the top edge and height using ``y`` and ``height`` attributes, vertical
centre and height using ``yc`` and ``height`` attributes, or top and bottom
edges using ``top`` and ``bottom`` attributes.
<bounds x="455" y="120" width="11" height="7" />
<bounds left="455" top="120" right="466" bottom="127" />
These three ``bounds`` elements are equivalent::
Either the ``x`` or ``left`` attribute must be present to distinguish between
the two schemes. The ``width`` and ``height`` or ``right`` and ``bottom``
default to 1.0 if not supplied. It is an error if ``width`` or ``height`` are
negative, if ``right`` is less than ``left``, or if ``bottom`` is less than
``top``.
<bounds x="455" y="120" width="12" height="8" />
<bounds xc="461" yc="124" width="12" height="8" />
<bounds left="455" top="120" right="467" bottom="128" />
Its possible to use different schemes in the horizontal and vertical
directions. For example, these equivalent ``bounds`` elements are also valid::
<bounds x="455" top="120" width="12" bottom="128" />
<bounds left="455" yc="124" right="467" height="8" />
The ``width``/``height`` or ``right``/``bottom`` default to 1.0 if not supplied.
It is an error if ``width`` or ``height`` are negative, if ``right`` is less
than ``left``, or if ``bottom`` is less than ``top``.
.. _layout-concepts-colours:
@ -376,12 +386,12 @@ and rendering. An element may be used in multiple views, and may be used
multiple times within a view.
An elements appearance depends on its *state*. The state is an integer which
usually comes from an I/O port field or an emulated output (see the discussion
of :ref:`layout-parts-views` for information on connecting an element to an I/O
port or output). Any component of an element may be restricted to only drawing
when the elements state is a particular value. Some components (e.g.
multi-segment displays and reels) use the state directly to determine their
appearance.
usually comes from an I/O port field or an emulated output (see
:ref:`layout-interact-elemstate` for information on connecting an element to an
emulated I/O port or output). Any component of an element may be restricted to
only drawing when the elements state is a particular value. Some components
(e.g. multi-segment displays and reels) use the state directly to determine
their appearance.
Each element has its own internal coordinate system. The bounds of the
elements coordinate system are computed as the union of the bounds of the
@ -395,8 +405,9 @@ attribute, to be used if not connected to an emulated output or I/O port. If
present, the ``defstate`` attribute must be a non-negative integer.
Child elements of the ``element`` element instantiate components, which are
drawn in reading order from first to last (components draw on top of components
that come before them). All components support a few common features:
drawn into the element texture in reading order from first to last using alpha
blending (components draw over and may obscure components that come before
them). All components support a few common features:
* Components may be conditionally drawn depending on the elements state by
supplying ``state`` and/or ``statemask`` attributes. If present, these
@ -459,18 +470,25 @@ rect
disk
Draws a uniform colour ellipse fitted to its bounds.
image
Draws an image loaded from a PNG or JPEG file. The name of the file to load
(including the file name extension) is supplied with the required ``file``
attribute. Additionally, an optional ``alphafile`` attribute may be used to
specify the name of a PNG file (including the file name extension) to load
into the alpha channel of the image. The image file(s) should be placed in
the same directory/archive as the layout file. If the ``alphafile``
attribute refers refers to a file, it must have the same dimensions as the
file referred to by the ``file`` attribute, and must have a bit depth no
greater than eight bits per channel per pixel. The intensity from this
image (brightness) is copied to the alpha channel, with full intensity (white
in a greyscale image) corresponding to fully opaque, and black corresponding
to fully transparent.
Draws an image loaded from a PNG, JPEG, Windows DIB (BMP) or SVG file. The
name of the file to load (including the file name extension) is supplied
using the required ``file`` attribute. Additionally, an optional
``alphafile`` attribute may be used to specify the name of a PNG file
(including the file name extension) to load into the alpha channel of the
image.
If the ``alphafile`` attribute refers refers to a file, it must have the
same dimensions (in pixels) as the file referred to by the ``file``
attribute, and must have a bit depth no greater than eight bits per channel
per pixel. The intensity from this image (brightness) is copied to the
alpha channel, with full intensity (white in a greyscale image)
corresponding to fully opaque, and black corresponding to fully transparent.
The ``alphafile`` attribute will be ignored if the ``file`` attribute refers
to an SVG image; it is only used in conjunction with bitmap images.
The image file(s) should be placed in the same directory/archive as the
layout file. Image file formats are detected by examining the content of
the files, file name extensions are ignored.
text
Draws text in using the UI font in the specified colour. The text to draw
must be supplied using a ``string`` attribute. An ``align`` attribute may
@ -680,11 +698,15 @@ param
element
Adds an element to the view (see :ref:`layout-parts-elements`). The name of
the element to add is specified using the required ``ref`` attribute. It is
an error if no element with this name is defined in the layout file. May
optionally be connected to an emulated I/O port using ``inputtag`` and
an error if no element with this name is defined in the layout file. Within
a layer, elements are drawn in the order they appear in the layout file,
from front to back. See below for more details.
May optionally be connected to an emulated I/O port using ``inputtag`` and
``inputmask`` attributes, and/or an emulated output using a ``name``
attribute. Within a layer, elements are drawn in the order they appear in
the layout file, from front to back. See below for more details.
attribute. See :ref:`layout-interact-clickable` for details. See
:ref:`layout-interact-elemstate` for details on supplying a state value to
the instantiated element.
screen
Adds an emulated screen image to the view. The screen must be identified
using either an ``index`` attribute or a ``tag`` attribute (it is an error
@ -694,6 +716,10 @@ screen
zero (0). If present, the ``tag`` attribute must be the tag path to the
screen relative to the device that causes the layout to be loaded. Screens
are drawn in the order they appear in the layout file, from front to back.
May optionally be connected to an emulated I/O port using ``inputtag`` and
``inputmask`` attributes, and/or an emulated output using a ``name``
attribute. See :ref:`layout-interact-clickable` for details.
collection
Adds screens and/or items in a collection that can be shown or hidden by the
user (see :ref:`layout-parts-collections`). The name of the collection is
@ -765,51 +791,10 @@ Screens (``screen`` elements), layout elements (``element`` elements) and groups
:ref:`layout-concepts-colours`) specifying a modifier colour. The component
colours of the screen or layout element(s) are multiplied by this colour.
If an ``element`` element has ``inputtag`` and ``inputmask`` attributes,
clicking it is equivalent to pressing a key/button mapped to the corresponding
input(s). The ``inputtag`` specifies the tag path of an I/O port relative to
the device that caused the layout file to be loaded. The ``inputmask``
attribute must be an integer specifying the bits of the I/O port that the
element should activate. This sample shows instantiation of clickable buttons::
<element ref="btn_3" inputtag="X2" inputmask="0x10">
<bounds x="2.30" y="4.325" width="1.0" height="1.0" />
</element>
<element ref="btn_0" inputtag="X0" inputmask="0x20">
<bounds x="0.725" y="5.375" width="1.0" height="1.0" />
</element>
<element ref="btn_rst" inputtag="RESET" inputmask="0x01">
<bounds x="1.775" y="5.375" width="1.0" height="1.0" />
</element>
If an ``element`` element has a ``name`` attribute, it will take its state from
the value of the correspondingly named emulated output. Note that output names
are global, which can become an issue when a machine uses multiple instances of
the same type of device. See :ref:`layout-parts-elements` for details on how an
elements state affects its appearance. This example shows how digital displays
may be connected to emulated outputs::
<element name="digit6" ref="digit"><bounds x="16" y="16" width="48" height="80" /></element>
<element name="digit5" ref="digit"><bounds x="64" y="16" width="48" height="80" /></element>
<element name="digit4" ref="digit"><bounds x="112" y="16" width="48" height="80" /></element>
<element name="digit3" ref="digit"><bounds x="160" y="16" width="48" height="80" /></element>
<element name="digit2" ref="digit"><bounds x="208" y="16" width="48" height="80" /></element>
<element name="digit1" ref="digit"><bounds x="256" y="16" width="48" height="80" /></element>
If an element instantiating a layout element has ``inputtag`` and ``inputmask``
attributes but lacks a ``name`` attribute, it will take its state from the value
of the corresponding I/O port, masked with the ``inputmask`` value and XORed
with the I/O port default field value. The latter is useful for inputs that are
active-low. If the result is non-zero, the state is 1, otherwise its 0. This
is often used to allow clickable buttons and toggle switches to provide visible
feedback. By using ``inputraw="1"``, its possible to obtain the raw data from
the I/O port, masked with the ``inputmask`` value and shifted to the right to
remove trailing zeroes (for example a mask of 0x05 will result in no shift, while
a mask of 0xb0 will result in the value being shifted four bits to the right).
When handling mouse input, MAME treats all layout elements as being rectangular,
and only activates the frontmost element whose area includes the location of the
mouse pointer.
Screens (``screen`` elements) and layout elements (``element`` elements) may
have their colour and position/size animated by supplying multiple ``color``
and/or ``bounds`` child elements with ``state`` attributes. See
:ref:`layout-interact-itemanim` for details.
.. _layout-parts-collections:
@ -1087,6 +1072,207 @@ tiles on each iteration. Rows are connected to I/O ports ``board:IN.7`` at the
top to ``board.IN.0`` at the bottom.
.. _layout-interact:
Interactivity
-------------
Interactive views are supported by allowing items to be bound to emulated
outputs and I/O ports. Five kinds of interactivity are supported:
Clickable items
If an item in a view is bound to an I/O port switch field, clicking the
item will activate the emulated switch.
State-dependent components
Some components will be drawn differently depending on the containing
elements state. These include the dot matrix, multi-segment LED display,
simple counter and reel elements. See :ref:`layout-parts-elements` for
details.
Conditionally-drawn components
Components may be conditionally drawn or hidden depending on the containing
elements state by supplying ``state`` and/or ``statemask`` attributes. See
:ref:`layout-parts-elements` for details.
Component parameter animation
Components colour and position/size within their containing element may be
animated according the elements state by providing multiple ``color``
and/or ``bounds`` elements with ``state`` attributes. See
:ref:`layout-parts-elements` for details.
Item parameter animation
Items colour and position/size within their containing view may be animated
according to their animation state.
.. _layout-interact-clickable:
Clickable items
~~~~~~~~~~~~~~~
If a view item (``element`` or ``screen`` element) has ``inputtag`` and
``inputmask`` attribute values that correspond to a digital switch field in the
emulated system, clicking the element will activate the switch. The switch
will remain active as long as the mouse button is held down and the pointer is
within the items current bounds. (Note that the bounds may change depending on
the items animation state, see :ref:`layout-interact-itemanim`).
The ``inputtag`` attribute specifies the tag path of an I/O port relative to the
device that caused the layout file to be loaded. The ``inputmask`` attribute
must be an integer specifying the bits of the I/O port field that the item
should activate. This sample shows instantiation of clickable buttons::
<element ref="btn_3" inputtag="X2" inputmask="0x10">
<bounds x="2.30" y="4.325" width="1.0" height="1.0" />
</element>
<element ref="btn_0" inputtag="X0" inputmask="0x20">
<bounds x="0.725" y="5.375" width="1.0" height="1.0" />
</element>
<element ref="btn_rst" inputtag="RESET" inputmask="0x01">
<bounds x="1.775" y="5.375" width="1.0" height="1.0" />
</element>
When handling mouse input, MAME treats all layout elements as being rectangular,
and only activates the first clickable item whose area includes the location of
the mouse pointer.
.. _layout-interact-elemstate:
Element state
~~~~~~~~~~~~~
A view item that instantiates an element (``element`` element) may supply a
state value to the element from an emulated I/O port or output. See
:ref:`layout-parts-elements` for details on how an elements state affects its
appearance.
If the ``element`` element has a ``name`` attribute, the element state value
will be taken from the value of the correspondingly named emulated output. Note
that output names are global, which can become an issue when a machine uses
multiple instances of the same type of device. This example shows how digital
displays may be connected to emulated outputs::
<element name="digit6" ref="digit"><bounds x="16" y="16" width="48" height="80" /></element>
<element name="digit5" ref="digit"><bounds x="64" y="16" width="48" height="80" /></element>
<element name="digit4" ref="digit"><bounds x="112" y="16" width="48" height="80" /></element>
<element name="digit3" ref="digit"><bounds x="160" y="16" width="48" height="80" /></element>
<element name="digit2" ref="digit"><bounds x="208" y="16" width="48" height="80" /></element>
<element name="digit1" ref="digit"><bounds x="256" y="16" width="48" height="80" /></element>
If the ``element`` element has ``inputtag`` and ``inputmask`` attributes but
lacks a ``name`` attribute, the element state value will be taken from the value
of the corresponding I/O port, masked with the ``inputmask`` value. The
``inputtag`` attribute specifies the tag path of an I/O port relative to the
device that caused the layout file to be loaded. The ``inputmask`` attribute
must be an integer specifying the bits of the I/O port field to use.
If the ``element`` element has no ``inputraw`` attribute, or if the value of the
``inputraw`` attribute is ``no``, the I/O ports value is masked with the
``inputmask`` value and XORed with the I/O port default field value. If the
result is non-zero, the element state is 1, otherwise its 0. This is often
used or provide visual feedback for clickable buttons, as values for active-high
and active-low switches are normalised.
If the ``element`` element has an ``inputraw`` attribute with the value ``yes``,
the element state will be taken from the I/O ports value masked with the
``inputmask`` value and shifted to the right to remove trailing zeroes (for
example a mask of 0x05 will result in no shift, while a mask of 0xb0 will result
in the value being shifted four bits to the right). This is useful for
obtaining the value of analog or positional inputs.
.. _layout-interact-itemanim:
View item animation
~~~~~~~~~~~~~~~~~~~
Items colour and position/size within their containing view may be animated.
This is achieved by supplying multiple ``color`` and/or ``bounds`` child
elements with ``state`` attributes. The ``state`` attribute of each ``color``
or ``bounds`` child element must be a non-negative integer. Withing a view
item, no two ``color`` elements may have equal state ``state`` attributes, and
no two ``bounds`` elements may have equal ``state`` attributes.
If the items animation state is lower than the ``state`` value of any
``bounds`` child element, the position/size specified by the ``bounds`` child
element with the lowest ``state`` value will be used. If the items
animation state is higher than the ``state`` value of any ``bounds`` child
element, the position/size specified by the ``bounds`` child element with the
highest ``state`` value will be used. If the items animation state is between
the ``state`` values of two ``bounds`` child elements, the position/size will be
interpolated linearly.
If the items animation state is lower than the ``state`` value of any ``color``
child element, the colour specified by the ``color`` child element with the
lowest ``state`` value will be used. If the items animation state is higher
than the ``state`` value of any ``color`` child element, the colour specified by
the ``color`` child element with the highest ``state`` value will be used. If
the items animation state is between the ``state`` values of two ``color``
child elements, the RGBA colour components will be interpolated linearly.
An items animation state may be bound to an emulated output or input port by
supplying an ``animate`` child element. If present, the ``animate`` element
must have either an ``inputtag`` attribute or a ``name`` attribute (but not
both). If the ``animate`` child element is not present, the items animation
state is the same as its element state (see :ref:`layout-interact-elemstate`).
If the ``animate`` child element is present and has an ``inputtag``
attribute, the items animation state will be taken from the value of the
corresponding I/O port. The ``inputtag`` attribute specifies the tag path of an
I/O port relative to the device that caused the layout file to be loaded. The
raw value from the input port is used, active-low switch values are not
normalised.
If the ``animate`` child element is present and has a ``name`` attribute, the
items animation state will be taken from the value of the correspondingly named
emulated output. Note that output names are global, which can become an issue
when a machine uses multiple instances of the same type of device.
If the ``animate`` child element has a ``mask`` attribute, the items animation
state will be masked with the ``mask`` value and shifted to the right to remove
trailing zeroes (for example a mask of 0x05 will result in no shift, while a
mask of 0xb0 will result in the value being shifted four bits to the right).
Note that the ``mask`` attribute applies to output value (specified with the
``name`` attribute) as well as input port values (specified with the
``inputtag`` attribute). If the ``mask`` attribute is present, it must be an
integer value. If the ``mask`` attribute is not present, it is equivalent to
all 32 bits being set.
This example shows elements with independent element state and animation state,
using the animation state taken from emulated outputs to control their
position::
<repeat count="5">
<param name="x" start="10" increment="9" />
<param name="i" start="0" increment="1" />
<param name="mask" start="0x01" lshift="1" />
<element name="cg_sol~i~" ref="cosmo">
<animate name="cg_count~i~" />
<bounds state="0" x="~x~" y="10" width="6" height="7" />
<bounds state="255" x="~x~" y="48.5" width="6" height="7" />
</element>
<element ref="nothing" inputtag="FAKE1" inputmask="~mask~">
<animate name="cg_count~i~" />
<bounds state="0" x="~x~" y="10" width="6" height="7" />
<bounds state="255" x="~x~" y="48.5" width="6" height="7" />
</element>
</repeat>
This example shows elements with independent element state and animation state,
using the animation state taken from an emulated positional input to control
their positions::
<repeat count="4">
<param name="y" start="1" increment="3" />
<param name="n" start="0" increment="1" />
<element ref="ledr" name="~n~.7">
<animate inputtag="IN.1" mask="0x0f" />
<bounds state="0" x="0" y="~y~" width="1" height="1" />
<bounds state="11" x="16.5" y="~y~" width="1" height="1" />
</element>
</repeat>
.. _layout-errors:
Error handling

View File

@ -225,6 +225,8 @@ class LayoutChecker(Minifyer):
bottom = self.checkFloatAttribute('bounds', attrs, 'bottom', 1.0)
x = self.checkFloatAttribute('bounds', attrs, 'x', 0.0)
y = self.checkFloatAttribute('bounds', attrs, 'y', 0.0)
xc = self.checkFloatAttribute('bounds', attrs, 'xc', 0.0)
yc = self.checkFloatAttribute('bounds', attrs, 'yc', 0.0)
width = self.checkFloatAttribute('bounds', attrs, 'width', 1.0)
height = self.checkFloatAttribute('bounds', attrs, 'height', 1.0)
if (left is not None) and (right is not None) and (left > right):
@ -239,12 +241,14 @@ class LayoutChecker(Minifyer):
self.handleError('Element bounds attribute width "%s" is negative' % (attrs['width'], ))
if (height is not None) and (0.0 > height):
self.handleError('Element bounds attribute height "%s" is negative' % (attrs['height'], ))
if ('left' not in attrs) and ('x' not in attrs):
self.handleError('Element bounds has neither attribute left nor attribute x')
has_ltrb = ('left' in attrs) or ('top' in attrs) or ('right' in attrs) or ('bottom' in attrs)
has_origin_size = ('x' in attrs) or ('y' in attrs) or ('width' in attrs) or ('height' in attrs)
if has_ltrb and has_origin_size:
self.handleError('Element bounds has both left/top/right/bottom and origin/size attributes')
if (('left' in attrs) and (('x' in attrs) or ('xc' in attrs))) or (('x' in attrs) and ('xc' in attrs)):
self.handleError('Element bounds has multiple horizontal origin attributes (left/x/xc)')
if (('left' in attrs) and ('width' in attrs)) or ((('x' in attrs) or ('xc' in attrs)) and ('right' in attrs)):
self.handleError('Element bounds has both left/right and x/xc/width attributes')
if (('top' in attrs) and (('y' in attrs) or ('yc' in attrs))) or (('y' in attrs) and ('yc' in attrs)):
self.handleError('Element bounds has multiple vertical origin attributes (top/y/yc)')
if (('top' in attrs) and ('height' in attrs)) or ((('y' in attrs) or ('yc' in attrs)) and ('bottom' in attrs)):
self.handleError('Element bounds has both top/bottom and y/yc/height attributes')
def checkOrientation(self, attrs):
if self.have_orientation[-1]:
@ -291,8 +295,32 @@ class LayoutChecker(Minifyer):
self.have_bounds.append({ })
self.have_color.append({ })
def startObject(self, name):
self.handlers.append((self.objectStartHandler, self.objectEndHandler))
def checkViewItem(self, name, attrs):
if ('blend' in attrs) and (attrs['blend'] not in self.BLENDMODES) and not self.VARPATTERN.match(attrs['blend']):
self.handleError('Element %s attribute blend "%s" is unsupported' % (name, attrs['blend']))
if 'inputtag' in attrs:
if 'inputmask' not in attrs:
self.handleError('Element %s has inputtag attribute without inputmask attribute' % (name, ))
self.checkTag(attrs['inputtag'], name, 'inputtag')
elif 'inputmask' in attrs:
self.handleError('Element %s has inputmask attribute without inputtag attribute' % (name, ))
inputraw = None
if 'inputraw' in attrs:
if (attrs['inputraw'] not in self.YESNO) and (not self.VARPATTERN.match(attrs['inputraw'])):
self.handleError('Element %s attribute inputraw "%s" is not "yes" or "no"' % (name, attrs['inputraw']))
else:
inputraw = 'yes' == attrs['inputraw']
if 'inputmask' not in attrs:
self.handleError('Element %s has inputraw attribute without inputmask attribute' % (name, ))
if 'inputtag' not in attrs:
self.handleError('Element %s has inputraw attribute without inputtag attribute' % (name, ))
inputmask = self.checkIntAttribute(name, attrs, 'inputmask', None)
if (inputmask is not None) and (not inputmask):
if (inputraw is None) or (not inputraw):
self.handleError('Element %s attribute inputmask "%s" is zero' % (name, attrs['inputmask']))
def startViewItem(self, name):
self.handlers.append((self.viewItemStartHandler, self.viewItemEndHandler))
self.have_bounds.append(None if 'group' == name else { })
self.have_orientation.append(False)
self.have_color.append(None if 'group' == name else { })
@ -477,29 +505,8 @@ class LayoutChecker(Minifyer):
self.handleError('Element %s missing attribute ref' % (name, ))
elif attrs['ref'] not in self.referenced_elements:
self.referenced_elements[attrs['ref']] = self.formatLocation()
if ('blend' in attrs) and (attrs['blend'] not in self.BLENDMODES) and not self.VARPATTERN.match(attrs['blend']):
self.handleError('Element %s attribute blend "%s" is unsupported' % (name, attrs['blend']))
if 'inputtag' in attrs:
if 'inputmask' not in attrs:
self.handleError('Element %s has inputtag attribute without inputmask attribute' % (name, ))
self.checkTag(attrs['inputtag'], name, 'inputtag')
elif 'inputmask' in attrs:
self.handleError('Element %s has inputmask attribute without inputtag attribute' % (name, ))
inputraw = None
if 'inputraw' in attrs:
if (attrs['inputraw'] not in self.YESNO) and (not self.VARPATTERN.match(attrs['inputraw'])):
self.handleError('Element %s attribute inputraw "%s" is not "yes" or "no"' % (name, attrs['inputraw']))
else:
inputraw = 'yes' == attrs['inputraw']
if 'inputmask' not in attrs:
self.handleError('Element %s has inputraw attribute without inputmask attribute' % (name, ))
if 'inputtag' not in attrs:
self.handleError('Element %s has inputraw attribute without inputtag attribute' % (name, ))
inputmask = self.checkIntAttribute(name, attrs, 'inputmask', None)
if (inputmask is not None) and (0 == inputmask):
if (inputraw is None) or (0 == inputraw):
self.handleError('Element %s attribute inputmask "%s" is zero' % (name, attrs['inputmask']))
self.startObject(name)
self.checkViewItem(name, attrs)
self.startViewItem(name)
elif 'screen' == name:
if 'index' in attrs:
index = self.checkIntAttribute(name, attrs, 'index', None)
@ -512,7 +519,8 @@ class LayoutChecker(Minifyer):
self.checkTag(tag, name, 'tag')
if self.BADTAGPATTERN.search(tag):
self.handleError('Element screen attribute tag "%s" contains invalid characters' % (tag, ))
self.startObject(name)
self.checkViewItem(name, attrs)
self.startViewItem(name)
elif 'group' == name:
if 'ref' not in attrs:
self.handleError('Element group missing attribute ref')
@ -525,7 +533,7 @@ class LayoutChecker(Minifyer):
self.current_collections[n] = l
else:
self.handleError('Element group instantiates collection with duplicate name "%s" from %s (previous %s)' % (n, l, self.current_collections[n]))
self.startObject(name)
self.startViewItem(name)
elif 'repeat' == name:
if 'count' not in attrs:
self.handleError('Element repeat missing attribute count')
@ -577,8 +585,19 @@ class LayoutChecker(Minifyer):
self.have_bounds.pop()
self.handlers.pop()
def objectStartHandler(self, name, attrs):
if 'bounds' == name:
def viewItemStartHandler(self, name, attrs):
if 'animate' == name:
if isinstance(self.have_bounds[-1], dict):
if 'inputtag' in attrs:
if 'name' in attrs:
self.handleError('Element animate has both attribute inputtag and attribute name')
self.checkTag(attrs['inputtag'], name, 'inputtag')
elif 'name' not in attrs:
self.handleError('Element animate has neither attribute inputtag nor attribute name')
self.checkIntAttribute(name, attrs, 'mask', None)
else:
self.handleError('Encountered unexpected element %s' % (name, ))
elif 'bounds' == name:
if self.have_bounds[-1] is None:
self.have_bounds[-1] = self.formatLocation()
elif isinstance(self.have_bounds[-1], dict):
@ -595,7 +614,7 @@ class LayoutChecker(Minifyer):
self.checkBounds(attrs)
elif 'orientation' == name:
self.checkOrientation(attrs)
if 'color' == name:
elif 'color' == name:
if self.have_color[-1] is None:
self.have_color[-1] = self.formatLocation()
elif isinstance(self.have_color[-1], dict):
@ -610,9 +629,11 @@ class LayoutChecker(Minifyer):
else:
self.handleError('Duplicate element color (previous %s)' % (self.have_color[-1], ))
self.checkColor(attrs)
else:
self.handleError('Encountered unexpected element %s' % (name, ))
self.ignored_depth = 1
def objectEndHandler(self, name):
def viewItemEndHandler(self, name):
self.have_bounds.pop()
self.have_orientation.pop()
self.have_color.pop()

View File

@ -181,6 +181,7 @@ files {
MAME_DIR .. "src/emu/recording.h",
MAME_DIR .. "src/emu/render.cpp",
MAME_DIR .. "src/emu/render.h",
MAME_DIR .. "src/emu/rendertypes.h",
MAME_DIR .. "src/emu/rendfont.cpp",
MAME_DIR .. "src/emu/rendfont.h",
MAME_DIR .. "src/emu/rendlay.cpp",

View File

@ -74,7 +74,10 @@ project "utils"
MAME_DIR .. "src/lib/util/jedparse.h",
MAME_DIR .. "src/lib/util/md5.cpp",
MAME_DIR .. "src/lib/util/md5.h",
MAME_DIR .. "src/lib/util/msdib.cpp",
MAME_DIR .. "src/lib/util/msdib.h",
MAME_DIR .. "src/lib/util/nanosvg.cpp",
MAME_DIR .. "src/lib/util/nanosvg.h",
MAME_DIR .. "src/lib/util/opresolv.cpp",
MAME_DIR .. "src/lib/util/opresolv.h",
MAME_DIR .. "src/lib/util/options.cpp",

View File

@ -10,7 +10,7 @@
#include "emu.h"
#include "picture.h"
#include "png.h"
#include "rendutil.h"
/***************************************************************************
TYPE DEFINITIONS
@ -44,15 +44,25 @@ void picture_image_device::device_start()
image_init_result picture_image_device::call_load()
{
if (png_read_bitmap(image_core_file(), m_picture) != PNGERR_NONE)
{
m_picture.reset();
switch (render_detect_image(image_core_file()))
{
case RENDUTIL_IMGFORMAT_PNG:
render_load_png(m_picture, image_core_file());
break;
// todo: try JPEG here.
return image_init_result::FAIL;
case RENDUTIL_IMGFORMAT_JPEG:
render_load_jpeg(m_picture, image_core_file());
break;
case RENDUTIL_IMGFORMAT_MSDIB:
render_load_msdib(m_picture, image_core_file());
break;
default:
m_picture.reset();
}
return image_init_result::PASS;
return m_picture.valid() ? image_init_result::PASS : image_init_result::FAIL;
}
void picture_image_device::call_unload()

View File

@ -14,10 +14,11 @@
***************************************************************************/
#include "emu.h"
#include "ui/uimain.h"
#include "rendutil.h"
#include "fixfreq.h"
#include "render.h"
#include "ui/uimain.h"
// for quick and dirty debugging
#define VERBOSE 0
#define LOG_OUTPUT_STREAM std::cerr

View File

@ -42,10 +42,11 @@
**************************************************************************** */
#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "vector.h"
#include "emuopts.h"
#include "render.h"
#define VECTOR_WIDTH_DENOM 512

View File

@ -9,12 +9,16 @@
***************************************************************************/
#include "emu.h"
#include "emuopts.h"
#include "rendutil.h"
#include "config.h"
#include "xmlfile.h"
#include "crsshair.h"
#include "config.h"
#include "emuopts.h"
#include "render.h"
#include "rendutil.h"
#include "screen.h"
#include "xmlfile.h"
/***************************************************************************
@ -171,28 +175,42 @@ void render_crosshair::create_bitmap()
rgb_t color = m_player < ARRAY_LENGTH(crosshair_colors) ? crosshair_colors[m_player] : rgb_t::white();
// if we have a bitmap and texture for this player, kill it
if (m_bitmap == nullptr)
if (!m_bitmap)
{
m_bitmap = std::make_unique<bitmap_argb32>();
m_texture = m_machine.render().texture_alloc(render_texture::hq_scale);
}
else
{
m_bitmap->reset();
}
emu_file crossfile(m_machine.options().crosshair_path(), OPEN_FLAG_READ);
if (!m_name.empty())
{
// look for user specified file
std::string filename = m_name + ".png";
render_load_png(*m_bitmap, crossfile, nullptr, filename.c_str());
if (crossfile.open(m_name + ".png") == osd_file::error::NONE)
{
render_load_png(*m_bitmap, crossfile);
crossfile.close();
}
}
else
{
// look for default cross?.png in crsshair/game dir
std::string filename = string_format("cross%d.png", m_player + 1);
render_load_png(*m_bitmap, crossfile, m_machine.system().name, filename.c_str());
std::string const filename = string_format("cross%d.png", m_player + 1);
if (crossfile.open(m_machine.system().name + (PATH_SEPARATOR + filename)) == osd_file::error::NONE)
{
render_load_png(*m_bitmap, crossfile);
crossfile.close();
}
// look for default cross?.png in crsshair dir
if (!m_bitmap->valid())
render_load_png(*m_bitmap, crossfile, nullptr, filename.c_str());
if (!m_bitmap->valid() && (crossfile.open(filename) == osd_file::error::NONE))
{
render_load_png(*m_bitmap, crossfile);
crossfile.close();
}
}
/* if that didn't work, use the built-in one */

View File

@ -248,7 +248,7 @@ mng_movie_recording::mng_movie_recording(screen_device *screen, std::map<std::st
mng_movie_recording::~mng_movie_recording()
{
if (m_mng_file)
mng_capture_stop(*m_mng_file);
util::mng_capture_stop(*m_mng_file);
}
@ -264,10 +264,10 @@ bool mng_movie_recording::initialize(std::unique_ptr<emu_file> &&file, bitmap_t
set_frame_period(attotime::from_hz(rate));
m_mng_file = std::move(file);
png_error pngerr = mng_capture_start(*m_mng_file, snap_bitmap, rate);
if (pngerr != PNGERR_NONE)
osd_printf_error("Error capturing MNG, png_error=%d\n", pngerr);
return pngerr == PNGERR_NONE;
util::png_error pngerr = util::mng_capture_start(*m_mng_file, snap_bitmap, rate);
if (pngerr != util::png_error::NONE)
osd_printf_error("Error capturing MNG, png_error=%d\n", std::underlying_type_t<util::png_error>(pngerr));
return pngerr == util::png_error::NONE;
}
@ -278,15 +278,15 @@ bool mng_movie_recording::initialize(std::unique_ptr<emu_file> &&file, bitmap_t
bool mng_movie_recording::append_single_video_frame(bitmap_rgb32 &bitmap, const rgb_t *palette, int palette_entries)
{
// set up the text fields in the movie info
png_info pnginfo;
util::png_info pnginfo;
if (current_frame() == 0)
{
for (auto &ent : m_info_fields)
pnginfo.add_text(ent.first.c_str(), ent.second.c_str());
}
png_error error = mng_capture_frame(*m_mng_file, pnginfo, bitmap, palette_entries, palette);
return error == png_error::PNGERR_NONE;
util::png_error error = util::mng_capture_frame(*m_mng_file, pnginfo, bitmap, palette_entries, palette);
return error == util::png_error::NONE;
}

View File

@ -1371,9 +1371,9 @@ render_primitive_list &render_target::get_primitives()
{
// if we are not in the running stage, draw an outer box
render_primitive *prim = list.alloc(render_primitive::QUAD);
set_render_bounds_xy(prim->bounds, 0.0f, 0.0f, (float)m_width, (float)m_height);
prim->bounds.set_xy(0.0f, 0.0f, (float)m_width, (float)m_height);
prim->full_bounds = prim->bounds;
set_render_color(&prim->color, 1.0f, 0.1f, 0.1f, 0.1f);
prim->color.set(1.0f, 0.1f, 0.1f, 0.1f);
prim->texture.base = nullptr;
prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
list.append(*prim);
@ -1381,9 +1381,9 @@ render_primitive_list &render_target::get_primitives()
if (m_width > 1 && m_height > 1)
{
prim = list.alloc(render_primitive::QUAD);
set_render_bounds_xy(prim->bounds, 1.0f, 1.0f, (float)(m_width - 1), (float)(m_height - 1));
prim->bounds.set_xy(1.0f, 1.0f, float(m_width - 1), float(m_height - 1));
prim->full_bounds = prim->bounds;
set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f);
prim->color.set(1.0f, 0.0f, 0.0f, 0.0f);
prim->texture.base = nullptr;
prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
list.append(*prim);
@ -1535,7 +1535,7 @@ bool render_target::map_point_input(s32 target_x, s32 target_y, ioport_port *&in
if (item.has_input())
{
// point successfully mapped
input_port = item.input_tag_and_mask(input_mask);
std::tie(input_port, input_mask) = item.input_tag_and_mask();
input_x = (target_f.first - bounds.x0) / bounds.width();
input_y = (target_f.second - bounds.y0) / bounds.height();
return true;
@ -2237,7 +2237,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const
cliprect.y0 = xform.yoffs;
cliprect.x1 = xform.xoffs + xform.xscale;
cliprect.y1 = xform.yoffs + xform.yscale;
sect_render_bounds(cliprect, m_bounds);
cliprect &= m_bounds;
float root_xoffs = root_xform.xoffs + fabsf(root_xform.xscale - xform.xscale) * 0.5f;
float root_yoffs = root_xform.yoffs + fabsf(root_xform.yscale - xform.yscale) * 0.5f;
@ -2247,7 +2247,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const
root_cliprect.y0 = root_yoffs;
root_cliprect.x1 = root_xoffs + root_xform.xscale;
root_cliprect.y1 = root_yoffs + root_xform.yscale;
sect_render_bounds(root_cliprect, m_bounds);
root_cliprect &= m_bounds;
// compute the container transform
object_transform container_xform;
@ -2477,7 +2477,7 @@ void render_target::add_container_primitives(render_primitive_list &list, const
// allocate a primitive
render_primitive *prim = list.alloc(render_primitive::QUAD);
set_render_bounds_wh(prim->bounds, xform.xoffs, xform.yoffs, xform.xscale, xform.yscale);
prim->bounds.set_wh(xform.xoffs, xform.yoffs, xform.xscale, xform.yscale);
prim->full_bounds = prim->bounds;
prim->color = container_xform.color;
width = render_round_nearest(prim->bounds.x1) - render_round_nearest(prim->bounds.x0);
@ -2525,7 +2525,7 @@ void render_target::add_element_primitives(render_primitive_list &list, const ob
// compute the bounds
s32 width = render_round_nearest(xform.xscale);
s32 height = render_round_nearest(xform.yscale);
set_render_bounds_wh(prim->bounds, render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), (float) width, (float) height);
prim->bounds.set_wh(render_round_nearest(xform.xoffs), render_round_nearest(xform.yoffs), float(width), float(height));
prim->full_bounds = prim->bounds;
if (xform.orientation & ORIENTATION_SWAP_XY)
std::swap(width, height);
@ -2542,7 +2542,7 @@ void render_target::add_element_primitives(render_primitive_list &list, const ob
cliprect.y0 = render_round_nearest(xform.yoffs);
cliprect.x1 = render_round_nearest(xform.xoffs + xform.xscale);
cliprect.y1 = render_round_nearest(xform.yoffs + xform.yscale);
sect_render_bounds(cliprect, m_bounds);
cliprect &= m_bounds;
// determine UV coordinates and apply clipping
prim->texcoords = oriented_texcoords[xform.orientation];
@ -2956,9 +2956,9 @@ void render_target::add_clear_extents(render_primitive_list &list)
if (x1 - x0 > 0)
{
render_primitive *prim = list.alloc(render_primitive::QUAD);
set_render_bounds_xy(prim->bounds, (float)x0, (float)y0, (float)x1, (float)y1);
prim->bounds.set_xy(float(x0), float(y0), float(x1), float(y1));
prim->full_bounds = prim->bounds;
set_render_color(&prim->color, 1.0f, 0.0f, 0.0f, 0.0f);
prim->color.set(1.0f, 0.0f, 0.0f, 0.0f);
prim->texture.base = nullptr;
prim->flags = PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA);
clearlist.append(*prim);
@ -3012,7 +3012,7 @@ void render_target::add_clear_and_optimize_primitive_list(render_primitive_list
if (PRIMFLAG_GET_BLENDMODE(prim.flags) == BLENDMODE_RGB_MULTIPLY)
{
// RGB multiply will multiply against 0, leaving nothing
set_render_color(&prim.color, 1.0f, 0.0f, 0.0f, 0.0f);
prim.color.set(1.0f, 0.0f, 0.0f, 0.0f);
prim.texture.base = nullptr;
prim.flags = (prim.flags & ~PRIMFLAG_BLENDMODE_MASK) | PRIMFLAG_BLENDMODE(BLENDMODE_NONE);
}

View File

@ -46,6 +46,7 @@
#ifndef MAME_EMU_RENDER_H
#define MAME_EMU_RENDER_H
#include "rendertypes.h"
#include "screen.h"
#include <array>
@ -65,17 +66,6 @@
// CONSTANTS
//**************************************************************************
// blending modes
enum
{
BLENDMODE_NONE = 0, // no blending
BLENDMODE_ALPHA, // standard alpha blend
BLENDMODE_RGB_MULTIPLY, // apply source alpha to source pix, then multiply RGB values
BLENDMODE_ADD, // apply source alpha to source pix, then add to destination
BLENDMODE_COUNT
};
// render creation flags
constexpr u8 RENDER_CREATE_NO_ART = 0x01; // ignore any views that have art in them
@ -166,62 +156,6 @@ constexpr u32 PRIMFLAG_GET_VECTORBUF(u32 x) { return (x & PRIMFLAG_VECTORBUF_MAS
// texture scaling callback
typedef void (*texture_scaler_func)(bitmap_argb32 &dest, bitmap_argb32 &source, const rectangle &sbounds, void *param);
// render_bounds - floating point bounding rectangle
struct render_bounds
{
float x0; // leftmost X coordinate
float y0; // topmost Y coordinate
float x1; // rightmost X coordinate
float y1; // bottommost Y coordinate
constexpr float width() const { return x1 - x0; }
constexpr float height() const { return y1 - y0; }
constexpr float aspect() const { return width() / height(); }
constexpr bool includes(float x, float y) const { return (x >= x0) && (x <= x1) && (y >= y0) && (y <= y1); }
};
// render_color - floating point set of ARGB values
struct render_color
{
float a; // alpha component (0.0 = transparent, 1.0 = opaque)
float r; // red component (0.0 = none, 1.0 = max)
float g; // green component (0.0 = none, 1.0 = max)
float b; // blue component (0.0 = none, 1.0 = max)
constexpr render_color operator*(render_color const &c) const
{
return render_color{ a * c.a, r * c.r, g * c.g, b * c.b };
}
render_color &operator*=(render_color const &c)
{
a *= c.a;
r *= c.r;
g *= c.g;
b *= c.b;
return *this;
}
};
// render_texuv - floating point set of UV texture coordinates
struct render_texuv
{
float u; // U coordinate (0.0-1.0)
float v; // V coordinate (0.0-1.0)
};
// render_quad_texuv - floating point set of UV texture coordinates
struct render_quad_texuv
{
render_texuv tl; // top-left UV coordinate
render_texuv tr; // top-right UV coordinate
render_texuv bl; // bottom-left UV coordinate
render_texuv br; // bottom-right UV coordinate
};
// render_texinfo - texture information
struct render_texinfo
@ -818,7 +752,7 @@ public:
// interactivity
bool has_input() const { return bool(m_input_port); }
ioport_port *input_tag_and_mask(ioport_value &mask) const { mask = m_input_mask; return m_input_port; };
std::pair<ioport_port *, ioport_value> input_tag_and_mask() const { return std::make_pair(m_input_port, m_input_mask); };
bool clickthrough() const { return m_clickthrough; }
// fetch state based on configured source
@ -831,13 +765,17 @@ public:
using bounds_vector = emu::render::detail::bounds_vector;
using color_vector = emu::render::detail::color_vector;
int animation_state() const;
static layout_element *find_element(view_environment &env, util::xml::data_node const &itemnode, element_map &elemmap);
static bounds_vector make_bounds(view_environment &env, util::xml::data_node const &itemnode, layout_group::transform const &trans);
static color_vector make_color(view_environment &env, util::xml::data_node const &itemnode, render_color const &mult);
static std::string make_animoutput_tag(view_environment &env, util::xml::data_node const &itemnode);
static std::string make_animinput_tag(view_environment &env, util::xml::data_node const &itemnode);
static ioport_value make_animmask(view_environment &env, util::xml::data_node const &itemnode);
static std::string make_input_tag(view_environment &env, util::xml::data_node const &itemnode);
static int get_blend_mode(view_environment &env, util::xml::data_node const &itemnode);
static unsigned get_input_shift(ioport_value mask);
static unsigned get_state_shift(ioport_value mask);
// internal state
layout_element *const m_element; // pointer to the associated element (non-screens only)
@ -845,6 +783,9 @@ public:
output_finder<> m_animoutput; // associated output for animation if different
bool const m_have_output; // whether we actually have an output
bool const m_have_animoutput; // whether we actually have an output for animation
ioport_port * m_animinput_port; // input port used for animation
ioport_value const m_animmask; // mask for animation state
u8 const m_animshift; // shift for animation state
ioport_port * m_input_port; // input port of this item
ioport_field const * m_input_field; // input port field of this item
ioport_value const m_input_mask; // input mask of this item
@ -860,6 +801,7 @@ public:
// cold items
std::string const m_input_tag; // input tag of this item
std::string const m_animinput_tag; // tag of input port for animation state
bounds_vector const m_rawbounds; // raw (original) bounds of the item
bool const m_has_clickthrough; // whether clickthrough was explicitly configured
};

153
src/emu/rendertypes.h Normal file
View File

@ -0,0 +1,153 @@
// license:BSD-3-Clause
// copyright-holders:Aaron Giles, Vas Crabb
/***************************************************************************
render.h
Core renderer constants and structures for MAME.
***************************************************************************/
#ifndef MAME_EMU_RENDERTYPES_H
#define MAME_EMU_RENDERTYPES_H
#pragma once
#include <algorithm>
//**************************************************************************
// CONSTANTS
//**************************************************************************
// blending modes
enum
{
BLENDMODE_NONE = 0, // no blending
BLENDMODE_ALPHA, // standard alpha blend
BLENDMODE_RGB_MULTIPLY, // apply source alpha to source pix, then multiply RGB values
BLENDMODE_ADD, // apply source alpha to source pix, then add to destination
BLENDMODE_COUNT
};
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// render_bounds - floating point bounding rectangle
struct render_bounds
{
float x0; // leftmost X coordinate
float y0; // topmost Y coordinate
float x1; // rightmost X coordinate
float y1; // bottommost Y coordinate
constexpr float width() const { return x1 - x0; }
constexpr float height() const { return y1 - y0; }
constexpr float aspect() const { return width() / height(); }
constexpr bool includes(float x, float y) const { return (x >= x0) && (x <= x1) && (y >= y0) && (y <= y1); }
// intersection
constexpr render_bounds operator&(render_bounds const &b) const
{
return render_bounds{ (std::max)(x0, b.x0), (std::max)(y0, b.y0), (std::min)(x1, b.x1), (std::min)(y1, b.y1) };
}
render_bounds &operator&=(render_bounds const &b)
{
x0 = (std::max)(x0, b.x0);
y0 = (std::max)(y0, b.y0);
x1 = (std::min)(x1, b.x1);
y1 = (std::min)(y1, b.y1);
return *this;
}
// union
constexpr render_bounds operator|(render_bounds const &b) const
{
return render_bounds{ (std::min)(x0, b.x0), (std::min)(y0, b.y0), (std::max)(x1, b.x1), (std::max)(y1, b.y1) };
}
render_bounds &operator|=(render_bounds const &b)
{
x0 = (std::min)(x0, b.x0);
y0 = (std::min)(y0, b.y0);
x1 = (std::max)(x1, b.x1);
y1 = (std::max)(y1, b.y1);
return *this;
}
render_bounds &set_xy(float left, float top, float right, float bottom)
{
x0 = left;
y0 = top;
x1 = right;
y1 = bottom;
return *this;
}
render_bounds &set_wh(float left, float top, float width, float height)
{
x0 = left;
y0 = top;
x1 = left + width;
y1 = top + height;
return *this;
}
};
// render_color - floating point set of ARGB values
struct render_color
{
float a; // alpha component (0.0 = transparent, 1.0 = opaque)
float r; // red component (0.0 = none, 1.0 = max)
float g; // green component (0.0 = none, 1.0 = max)
float b; // blue component (0.0 = none, 1.0 = max)
constexpr render_color operator*(render_color const &c) const
{
return render_color{ a * c.a, r * c.r, g * c.g, b * c.b };
}
render_color &operator*=(render_color const &c)
{
a *= c.a;
r *= c.r;
g *= c.g;
b *= c.b;
return *this;
}
render_color &set(float alpha, float red, float green, float blue)
{
a = alpha;
r = red;
g = green;
b = blue;
return *this;
}
};
// render_texuv - floating point set of UV texture coordinates
struct render_texuv
{
float u; // U coordinate (0.0-1.0)
float v; // V coordinate (0.0-1.0)
};
// render_quad_texuv - floating point set of UV texture coordinates
struct render_quad_texuv
{
render_texuv tl; // top-left UV coordinate
render_texuv tr; // top-right UV coordinate
render_texuv bl; // bottom-left UV coordinate
render_texuv br; // bottom-right UV coordinate
};
#endif // MAME_EMU_RENDERTYPES_H

View File

@ -17,6 +17,7 @@
#include "rendutil.h"
#include "video/rgbutil.h"
#include "nanosvg.h"
#include "vecstream.h"
#include "xmlfile.h"
@ -27,6 +28,7 @@
#include <cstdio>
#include <cstring>
#include <iomanip>
#include <limits>
#include <locale>
#include <sstream>
#include <stdexcept>
@ -578,25 +580,29 @@ private:
entry_vector m_entries;
util::ovectorstream m_buffer;
std::shared_ptr<NSVGrasterizer> const m_svg_rasterizer;
device_t &m_device;
layout_environment *const m_next = nullptr;
bool m_cached = false;
public:
explicit layout_environment(device_t &device)
: m_device(device)
: m_svg_rasterizer(nsvgCreateRasterizer(), util::nsvg_deleter())
, m_device(device)
{
}
explicit layout_environment(layout_environment &next)
: m_device(next.m_device)
: m_svg_rasterizer(next.m_svg_rasterizer)
, m_device(next.m_device)
, m_next(&next)
{
}
layout_environment(layout_environment const &) = delete;
device_t &device() { return m_device; }
running_machine &machine() { return device().machine(); }
bool is_root_device() { return &device() == &machine().root_device(); }
device_t &device() const { return m_device; }
running_machine &machine() const { return device().machine(); }
bool is_root_device() const { return &device() == &machine().root_device(); }
std::shared_ptr<NSVGrasterizer> const &svg_rasterizer() const { return m_svg_rasterizer; }
void set_parameter(std::string &&name, std::string &&value)
{
@ -785,34 +791,44 @@ public:
void parse_bounds(util::xml::data_node const *node, render_bounds &result)
{
// default to unit rectangle
if (!node)
{
// default to unit rectangle
result.x0 = result.y0 = 0.0F;
result.x1 = result.y1 = 1.0F;
}
else
{
// parse attributes
// horizontal position/size
if (node->has_attribute("left"))
{
// left/right/top/bottom format
result.x0 = get_attribute_float(*node, "left", 0.0F);
result.x1 = get_attribute_float(*node, "right", 1.0F);
result.y0 = get_attribute_float(*node, "top", 0.0F);
result.y1 = get_attribute_float(*node, "bottom", 1.0F);
}
else if (node->has_attribute("x"))
{
// x/y/width/height format
result.x0 = get_attribute_float(*node, "x", 0.0F);
result.x1 = result.x0 + get_attribute_float(*node, "width", 1.0F);
result.y0 = get_attribute_float(*node, "y", 0.0F);
result.y1 = result.y0 + get_attribute_float(*node, "height", 1.0F);
}
else
{
throw layout_syntax_error("bounds element requires either left or x attribute");
float const width = get_attribute_float(*node, "width", 1.0F);
if (node->has_attribute("xc"))
result.x0 = get_attribute_float(*node, "xc", 0.0F) - (width / 2.0F);
else
result.x0 = get_attribute_float(*node, "x", 0.0F);
result.x1 = result.x0 + width;
}
// vertical position/size
if (node->has_attribute("top"))
{
result.y0 = get_attribute_float(*node, "top", 0.0F);
result.y1 = get_attribute_float(*node, "bottom", 1.0F);
}
else
{
float const height = get_attribute_float(*node, "height", 1.0F);
if (node->has_attribute("yc"))
result.y0 = get_attribute_float(*node, "yc", 0.0F) - (height / 2.0F);
else
result.y0 = get_attribute_float(*node, "y", 0.0F);
result.y1 = result.y0 + height;
}
// check for errors
@ -978,7 +994,7 @@ render_bounds accumulate_bounds(emu::render::detail::bounds_vector const &steps)
auto i(steps.begin());
render_bounds result(i->bounds);
while (steps.end() != ++i)
union_render_bounds(result, i->bounds);
result |= i->bounds;
return result;
}
@ -1123,7 +1139,7 @@ layout_element::layout_element(environment &env, util::xml::data_node const &ele
if (first)
bounds = newcomp.overall_bounds();
else
union_render_bounds(bounds, newcomp.overall_bounds());
bounds |= newcomp.overall_bounds();
first = false;
// determine the maximum state
@ -1286,7 +1302,7 @@ void layout_group::resolve_bounds(environment &env, group_map &groupmap, std::ve
seen.push_back(this);
if (!m_bounds_resolved)
{
set_render_bounds_xy(m_bounds, 0.0F, 0.0F, 1.0F, 1.0F);
m_bounds.set_xy(0.0F, 0.0F, 1.0F, 1.0F);
environment local(env);
bool empty(true);
resolve_bounds(local, m_groupnode, groupmap, seen, empty, false, false, true);
@ -1349,13 +1365,13 @@ void layout_group::resolve_bounds(
{
render_bounds b;
env.parse_bounds(boundsnode, b);
union_render_bounds(itembounds, b);
itembounds |= b;
}
}
if (empty)
m_bounds = itembounds;
else
union_render_bounds(m_bounds, itembounds);
m_bounds |= itembounds;
empty = false;
LOGMASKED(LOG_GROUP_BOUNDS_RESOLUTION, "Accumulate item bounds (%s %s %s %s) -> (%s %s %s %s)\n",
itembounds.x0, itembounds.y0, itembounds.x1, itembounds.y1,
@ -1371,7 +1387,7 @@ void layout_group::resolve_bounds(
if (empty)
m_bounds = itembounds;
else
union_render_bounds(m_bounds, itembounds);
m_bounds |= itembounds;
empty = false;
LOGMASKED(LOG_GROUP_BOUNDS_RESOLUTION, "Accumulate group '%s' reference explicit bounds (%s %s %s %s) -> (%s %s %s %s)\n",
itemnode->get_attribute_string("ref", ""),
@ -1399,7 +1415,7 @@ void layout_group::resolve_bounds(
if (empty)
m_bounds = itembounds;
else
union_render_bounds(m_bounds, itembounds);
m_bounds |= itembounds;
empty = false;
unresolved = false;
LOGMASKED(LOG_GROUP_BOUNDS_RESOLUTION, "Accumulate group '%s' reference computed bounds (%s %s %s %s) -> (%s %s %s %s)\n",
@ -1523,11 +1539,13 @@ class layout_element::image_component : public component
{
public:
// construction/destruction
image_component(environment &env, util::xml::data_node const &compnode, const char *dirname)
image_component(environment &env, util::xml::data_node const &compnode, char const *dirname)
: component(env, compnode, dirname)
, m_rasterizer(env.svg_rasterizer())
, m_dirname(dirname ? dirname : "")
, m_imagefile(env.get_attribute_string(compnode, "file", ""))
, m_alphafile(env.get_attribute_string(compnode, "alphafile", ""))
, m_data(get_data(compnode))
{
}
@ -1535,51 +1553,30 @@ public:
virtual void preload(running_machine &machine) override
{
if (!m_bitmap.valid())
load_bitmap(machine);
load_image(machine);
}
virtual void draw(running_machine &machine, bitmap_argb32 &dest, const rectangle &bounds, int state) override
virtual void draw(running_machine &machine, bitmap_argb32 &dest, rectangle const &bounds, int state) override
{
if (!m_bitmap.valid())
load_bitmap(machine);
if (!m_bitmap.valid() && !m_svg)
load_image(machine);
if (m_bitmap.valid())
draw_bitmap(dest, bounds, state);
else if (m_svg)
draw_svg(dest, bounds, state);
}
private:
// internal helpers
void draw_bitmap(bitmap_argb32 &dest, rectangle const &bounds, int state)
{
render_color const c(color(state));
if (m_hasalpha || (1.0F > c.a))
{
bitmap_argb32 tempbitmap(dest.width(), dest.height());
render_resample_argb_bitmap_hq(tempbitmap, m_bitmap, c);
for (s32 y0 = 0, y1 = bounds.top(); bounds.bottom() >= y1; ++y0, ++y1)
{
u32 const *src(&tempbitmap.pix(y0, 0));
u32 *dst(&dest.pix(y1, bounds.left()));
for (s32 x1 = bounds.left(); bounds.right() >= x1; ++x1, ++src, ++dst)
{
rgb_t const a(*src);
u32 const aa(a.a());
if (255 == aa)
{
*dst = *src;
}
else if (aa)
{
rgb_t const b(*dst);
u32 const ba(b.a());
if (ba)
{
u32 const ca((aa * 255) + (ba * (255 - aa)));
*dst = rgb_t(
u8(ca / 255),
u8(((a.r() * aa * 255) + (b.r() * ba * (255 - aa))) / ca),
u8(((a.g() * aa * 255) + (b.g() * ba * (255 - aa))) / ca),
u8(((a.b() * aa * 255) + (b.b() * ba * (255 - aa))) / ca));
}
else
{
*dst = *src;
}
}
}
}
alpha_blend(tempbitmap, dest, bounds);
}
else
{
@ -1588,44 +1585,172 @@ public:
}
}
private:
// internal helpers
void load_bitmap(running_machine &machine)
void draw_svg(bitmap_argb32 &dest, rectangle const &bounds, int state)
{
LOGMASKED(LOG_IMAGE_LOAD, "Image component attempt to load image file '%s/%s'\n", m_dirname, m_imagefile);
emu_file file(machine.options().art_path(), OPEN_FLAG_READ);
// rasterise into a temporary bitmap
float const xscale(bounds.width() / m_svg->width);
float const yscale(bounds.height() / m_svg->height);
float const drawscale((std::max)(xscale, yscale));
bitmap_argb32 tempbitmap(int(m_svg->width * drawscale), int(m_svg->height * drawscale));
nsvgRasterize(
m_rasterizer.get(),
m_svg.get(),
0, 0, drawscale,
reinterpret_cast<unsigned char *>(&tempbitmap.pix(0)),
tempbitmap.width(), tempbitmap.height(),
tempbitmap.rowbytes());
ru_imgformat const format = render_detect_image(file, m_dirname.c_str(), m_imagefile.c_str());
switch (format)
// correct colour format and multiply by state colour
bool havealpha(false);
render_color const c(color(state));
for (s32 y = 0; tempbitmap.height() > y; ++y)
{
case RENDUTIL_IMGFORMAT_ERROR:
LOGMASKED(LOG_IMAGE_LOAD, "Image component error detecting image file format\n");
break;
u32 *dst(&tempbitmap.pix(y));
for (s32 x = 0; tempbitmap.width() > x; ++x, ++dst)
{
u8 const *const src(reinterpret_cast<u8 const *>(dst));
rgb_t const d(
u8((float(src[3]) * c.a) + 0.5),
u8((float(src[0]) * c.r) + 0.5),
u8((float(src[1]) * c.g) + 0.5),
u8((float(src[2]) * c.b) + 0.5));
*dst = d;
havealpha = havealpha || (d.a() < 255U);
}
}
case RENDUTIL_IMGFORMAT_PNG:
// load the basic bitmap
LOGMASKED(LOG_IMAGE_LOAD, "Image component detected PNG file format\n");
m_hasalpha = render_load_png(m_bitmap, file, m_dirname.c_str(), m_imagefile.c_str());
break;
// find most efficient way to insert it in the target bitmap
if (!havealpha)
{
if ((tempbitmap.width() == bounds.width()) && (tempbitmap.height() == bounds.height()))
{
for (s32 y = 0; tempbitmap.height() > y; ++y)
std::copy_n(&tempbitmap.pix(y), bounds.width(), &dest.pix(y + bounds.top(), bounds.left()));
}
else
{
bitmap_argb32 destsub(dest, bounds);
render_resample_argb_bitmap_hq(destsub, tempbitmap, render_color{ 1.0F, 1.0F, 1.0F, 1.0F });
}
}
else if ((tempbitmap.width() == bounds.width()) && (tempbitmap.height() == bounds.height()))
{
alpha_blend(tempbitmap, dest, bounds);
}
else
{
bitmap_argb32 scaled(bounds.width(), bounds.height());
render_resample_argb_bitmap_hq(scaled, tempbitmap, render_color{ 1.0F, 1.0F, 1.0F, 1.0F });
tempbitmap.reset();
alpha_blend(scaled, dest, bounds);
}
}
default:
// try JPG
LOGMASKED(LOG_IMAGE_LOAD, "Image component failed to detect image file format, trying JPEG\n");
render_load_jpeg(m_bitmap, file, m_dirname.c_str(), m_imagefile.c_str());
break;
void alpha_blend(bitmap_argb32 const &srcbitmap, bitmap_argb32 &dstbitmap, rectangle const &bounds)
{
for (s32 y0 = 0, y1 = bounds.top(); bounds.bottom() >= y1; ++y0, ++y1)
{
u32 const *src(&srcbitmap.pix(y0, 0));
u32 *dst(&dstbitmap.pix(y1, bounds.left()));
for (s32 x1 = bounds.left(); bounds.right() >= x1; ++x1, ++src, ++dst)
{
rgb_t const a(*src);
u32 const aa(a.a());
if (255 == aa)
{
*dst = *src;
}
else if (aa)
{
rgb_t const b(*dst);
u32 const ba(b.a());
if (ba)
{
u32 const ca((aa * 255) + (ba * (255 - aa)));
*dst = rgb_t(
u8(ca / 255),
u8(((a.r() * aa * 255) + (b.r() * ba * (255 - aa))) / ca),
u8(((a.g() * aa * 255) + (b.g() * ba * (255 - aa))) / ca),
u8(((a.b() * aa * 255) + (b.b() * ba * (255 - aa))) / ca));
}
else
{
*dst = *src;
}
}
}
}
}
void load_image(running_machine &machine)
{
// attempt to open the file
LOGMASKED(LOG_IMAGE_LOAD, "Image component attempt to load image file '%s%s%s'\n", m_dirname, PATH_SEPARATOR, m_imagefile);
emu_file file(machine.options().art_path(), OPEN_FLAG_READ);
osd_file::error const imgerr = file.open(m_dirname.empty() ? m_imagefile : (m_dirname + PATH_SEPARATOR + m_imagefile));
if (osd_file::error::NONE == imgerr)
{
ru_imgformat const format = render_detect_image(file);
switch (format)
{
case RENDUTIL_IMGFORMAT_ERROR:
LOGMASKED(LOG_IMAGE_LOAD, "Image component error detecting image file format\n");
break;
case RENDUTIL_IMGFORMAT_PNG:
LOGMASKED(LOG_IMAGE_LOAD, "Image component detected PNG file format\n");
m_hasalpha = render_load_png(m_bitmap, file);
break;
case RENDUTIL_IMGFORMAT_JPEG:
LOGMASKED(LOG_IMAGE_LOAD, "Image component detected JPEG file format\n");
render_load_jpeg(m_bitmap, file);
break;
case RENDUTIL_IMGFORMAT_MSDIB:
LOGMASKED(LOG_IMAGE_LOAD, "Image component detected Microsoft DIB file format\n");
render_load_msdib(m_bitmap, file);
break;
default:
LOGMASKED(LOG_IMAGE_LOAD, "Image component failed to detect image file format, trying SVG\n");
load_svg(file);
break;
}
file.close();
}
else
{
LOGMASKED(LOG_IMAGE_LOAD, "Image component unable to open image file '%s%s%s'\n", m_dirname, PATH_SEPARATOR, m_imagefile);
}
// load the alpha bitmap if specified
if (m_bitmap.valid() && !m_alphafile.empty())
if (!m_alphafile.empty())
{
// TODO: no way to detect corner case where we had alpha from the image but the alpha PNG makes it entirely opaque
LOGMASKED(LOG_IMAGE_LOAD, "Image component attempt to load alpha channel from file '%s/%s'\n", m_dirname, m_alphafile);
if (render_load_png(m_bitmap, file, m_dirname.c_str(), m_alphafile.c_str(), true))
m_hasalpha = true;
if (m_bitmap.valid())
{
LOGMASKED(LOG_IMAGE_LOAD, "Image component attempt to load alpha channel from file '%s%s%s'\n", m_dirname, PATH_SEPARATOR, m_alphafile);
osd_file::error const alferr = file.open(m_dirname.empty() ? m_alphafile : (m_dirname + PATH_SEPARATOR + m_alphafile));
if (osd_file::error::NONE == alferr)
{
// TODO: no way to detect corner case where we had alpha from the image but the alpha PNG makes it entirely opaque
if (render_load_png(m_bitmap, file, true))
m_hasalpha = true;
file.close();
}
else
{
LOGMASKED(LOG_IMAGE_LOAD, "Image component unable to open alpha channel file '%s%s%s'\n", m_dirname, PATH_SEPARATOR, m_alphafile);
}
}
else if (m_svg)
{
osd_printf_warning("Component alpha channel file '%s' ignored for SVG image '%s'\n", m_alphafile, m_imagefile);
}
}
// if we can't load the bitmap, allocate a dummy one and report an error
if (!m_bitmap.valid())
// if we can't load an image, allocate a dummy one and report an error
if (!m_bitmap.valid() && !m_svg)
{
// draw some stripes in the bitmap
m_bitmap.allocate(100, 100);
@ -1636,18 +1761,87 @@ private:
// log an error
if (m_alphafile.empty())
osd_printf_warning("Unable to load component bitmap '%s'\n", m_imagefile);
osd_printf_warning("Unable to load component image '%s'\n", m_imagefile);
else
osd_printf_warning("Unable to load component bitmap '%s'/'%s'\n", m_imagefile, m_alphafile);
osd_printf_warning("Unable to load component image '%s'/'%s'\n", m_imagefile, m_alphafile);
}
// clear out this stuff in case it's large
if (!m_svg)
m_rasterizer.reset();
m_dirname.clear();
m_imagefile.clear();
m_alphafile.clear();
m_data.clear();
}
void load_svg(util::core_file &file)
{
if (!m_rasterizer)
{
osd_printf_warning("No SVG rasteriser available, won't attempt to parse component image '%s' as SVG\n", m_imagefile);
return;
}
u64 len(file.size());
if ((std::numeric_limits<size_t>::max() - 1) < len)
{
osd_printf_warning("Component image '%s' is too large to read into memory\n", m_imagefile);
return;
}
std::unique_ptr<char []> svgbuf(new (std::nothrow) char [size_t(len) + 1]);
if (!svgbuf)
{
osd_printf_warning("Error allocating memory to read component image '%s'\n", m_imagefile);
return;
}
svgbuf[len] = '\0';
for (char *ptr = svgbuf.get(); len; )
{
u32 const block(u32(std::min<u64>(std::numeric_limits<u32>::max(), len)));
u32 const read(file.read(ptr, block));
if (!read)
{
osd_printf_warning("Error reading component image '%s'\n", m_imagefile);
return;
}
ptr += read;
len -= read;
}
m_svg.reset(nsvgParse(svgbuf.get(), "px", 72));
svgbuf.reset();
if (!m_svg)
{
osd_printf_warning("Failed to parse component image '%s' as SVG\n", m_imagefile);
return;
}
if ((0.0F >= m_svg->width) || (0.0F >= m_svg->height))
{
osd_printf_warning("Parsing component image '%s' as SVG produced empty image\n", m_imagefile);
m_svg.reset();
return;
}
}
static std::string get_data(util::xml::data_node const &compnode)
{
util::xml::data_node const *datanode(compnode.get_child("data"));
if (datanode && datanode->get_value())
return datanode->get_value();
else
return "";
}
// internal state
bitmap_argb32 m_bitmap; // source bitmap for images
std::string const m_dirname; // directory name of image file (for lazy loading)
std::string const m_imagefile; // name of the image file (for lazy loading)
std::string const m_alphafile; // name of the alpha file (for lazy loading)
bool m_hasalpha = false; // is there any alpha component present?
util::nsvg_image_ptr m_svg; // parsed SVG image
std::shared_ptr<NSVGrasterizer> m_rasterizer; // SVG rasteriser
bitmap_argb32 m_bitmap; // source bitmap for images
bool m_hasalpha = false; // is there any alpha component present?
// cold state
std::string m_dirname; // directory name of image file (for lazy loading)
std::string m_imagefile; // name of the image file (for lazy loading)
std::string m_alphafile; // name of the alpha file (for lazy loading)
std::string m_data; // embedded image data
};
@ -2881,7 +3075,11 @@ private:
{
// load the basic bitmap
assert(m_file != nullptr);
/*m_hasalpha[number] = */ render_load_png(m_bitmap[number], *m_file[number], m_dirname.c_str(), m_imagefile[number].c_str());
if (m_file[number]->open(m_dirname + PATH_SEPARATOR + m_imagefile[number]) == osd_file::error::NONE)
{
/*m_hasalpha[number] = */ render_load_png(m_bitmap[number], *m_file[number]);
m_file[number]->close();
}
// load the alpha bitmap if specified
//if (m_bitmap[number].valid() && m_alphafile[number])
@ -3593,7 +3791,7 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen)
if (first)
m_bounds = rawbounds;
else
union_render_bounds(m_bounds, rawbounds);
m_bounds |= rawbounds;
first = false;
// accumulate visible screens and their bounds bounds
@ -3602,7 +3800,7 @@ void layout_view::recompute(u32 visibility_mask, bool zoom_to_screen)
if (scrfirst)
scrbounds = rawbounds;
else
union_render_bounds(scrbounds, rawbounds);
scrbounds |= rawbounds;
scrfirst = false;
// accumulate active screens
@ -3912,10 +4110,13 @@ layout_view::item::item(
, m_animoutput(env.device(), make_animoutput_tag(env, itemnode))
, m_have_output(env.get_attribute_string(itemnode, "name", "")[0])
, m_have_animoutput(!make_animoutput_tag(env, itemnode).empty())
, m_animinput_port(nullptr)
, m_animmask(make_animmask(env, itemnode))
, m_animshift(get_state_shift(m_animmask))
, m_input_port(nullptr)
, m_input_field(nullptr)
, m_input_mask(env.get_attribute_int(itemnode, "inputmask", 0))
, m_input_shift(get_input_shift(m_input_mask))
, m_input_shift(get_state_shift(m_input_mask))
, m_input_raw(env.get_attribute_bool(itemnode, "inputraw", 0))
, m_clickthrough(env.get_attribute_bool(itemnode, "clickthrough", "yes"))
, m_screen(nullptr)
@ -3924,6 +4125,7 @@ layout_view::item::item(
, m_blend_mode(get_blend_mode(env, itemnode))
, m_visibility_mask(env.visibility_mask())
, m_input_tag(make_input_tag(env, itemnode))
, m_animinput_tag(make_animinput_tag(env, itemnode))
, m_rawbounds(make_bounds(env, itemnode, trans))
, m_has_clickthrough(env.get_attribute_string(itemnode, "clickthrough", "")[0])
{
@ -3972,7 +4174,7 @@ render_bounds layout_view::item::bounds() const
if (m_bounds.size() == 1U)
return m_bounds.front().bounds;
else
return interpolate_bounds(m_bounds, m_have_animoutput ? s32(m_animoutput) : state());
return interpolate_bounds(m_bounds, animation_state());
}
@ -3985,7 +4187,7 @@ render_color layout_view::item::color() const
if (m_color.size() == 1U)
return m_color.front().color;
else
return interpolate_color(m_color, m_have_animoutput ? s32(m_animoutput) : state());
return interpolate_color(m_color, animation_state());
}
@ -4038,6 +4240,9 @@ void layout_view::item::resolve_tags()
if (m_have_animoutput)
m_animoutput.resolve();
if (!m_animinput_tag.empty())
m_animinput_port = m_element->machine().root_device().ioport(m_animinput_tag);
if (!m_input_tag.empty())
{
m_input_port = m_element->machine().root_device().ioport(m_input_tag);
@ -4062,6 +4267,21 @@ void layout_view::item::resolve_tags()
}
//---------------------------------------------
// find_element - find element definition
//---------------------------------------------
inline int layout_view::item::animation_state() const
{
if (m_have_animoutput)
return (s32(m_animoutput) & m_animmask) >> m_animshift;
else if (m_animinput_port)
return (m_animinput_port->read() & m_animmask) >> m_animshift;
else
return state();
}
//---------------------------------------------
// find_element - find element definition
//---------------------------------------------
@ -4156,6 +4376,30 @@ std::string layout_view::item::make_animoutput_tag(view_environment &env, util::
}
//---------------------------------------------
// make_animmask - get animation state mask
//---------------------------------------------
ioport_value layout_view::item::make_animmask(view_environment &env, util::xml::data_node const &itemnode)
{
util::xml::data_node const *const animate(itemnode.get_child("animate"));
return animate ? env.get_attribute_int(*animate, "mask", ~ioport_value(0)) : ~ioport_value(0);
}
//---------------------------------------------
// make_animinput_tag - get absolute tag for
// animation input
//---------------------------------------------
std::string layout_view::item::make_animinput_tag(view_environment &env, util::xml::data_node const &itemnode)
{
util::xml::data_node const *const animate(itemnode.get_child("animate"));
char const *tag(animate ? env.get_attribute_string(*animate, "inputtag", nullptr) : nullptr);
return tag ? env.device().subtag(tag) : std::string();
}
//---------------------------------------------
// make_input_tag - get absolute input tag
//---------------------------------------------
@ -4200,10 +4444,10 @@ int layout_view::item::get_blend_mode(view_environment &env, util::xml::data_nod
//---------------------------------------------
// get_input_shift - shift to right-align LSB
// get_state_shift - shift to right-align LSB
//---------------------------------------------
unsigned layout_view::item::get_input_shift(ioport_value mask)
unsigned layout_view::item::get_state_shift(ioport_value mask)
{
unsigned result(0U);
while (mask && !BIT(mask, 0))

View File

@ -11,9 +11,109 @@
#include "emu.h"
#include "rendutil.h"
#include "msdib.h"
#include "png.h"
#include "jpeglib.h"
#include "jerror.h"
namespace {
struct jpeg_corefile_source : public jpeg_source_mgr
{
static void source(j_decompress_ptr cinfo, util::core_file &file);
private:
static constexpr unsigned INPUT_BUF_SIZE = 4096;
static void do_init(j_decompress_ptr cinfo)
{
jpeg_corefile_source &src = *static_cast<jpeg_corefile_source *>(cinfo->src);
src.start_of_file = true;
}
static boolean do_fill(j_decompress_ptr cinfo)
{
jpeg_corefile_source &src = *static_cast<jpeg_corefile_source *>(cinfo->src);
size_t nbytes = src.infile->read(src.buffer, INPUT_BUF_SIZE);
if (0 >= nbytes)
{
if (src.start_of_file)
ERREXIT(cinfo, JERR_INPUT_EMPTY);
WARNMS(cinfo, JWRN_JPEG_EOF);
src.buffer[0] = JOCTET(0xff);
src.buffer[1] = JOCTET(JPEG_EOI);
nbytes = 2;
}
src.next_input_byte = src.buffer;
src.bytes_in_buffer = nbytes;
src.start_of_file = false;
return TRUE;
}
static void do_skip(j_decompress_ptr cinfo, long num_bytes)
{
jpeg_corefile_source &src = *static_cast<jpeg_corefile_source *>(cinfo->src);
if (0 < num_bytes)
{
while (long(src.bytes_in_buffer) < num_bytes)
{
num_bytes -= long(src.bytes_in_buffer);
(void)(*src.fill_input_buffer)(cinfo);
}
src.next_input_byte += size_t(num_bytes);
src.bytes_in_buffer -= size_t(num_bytes);
}
}
static void do_term(j_decompress_ptr cinfo)
{
}
util::core_file *infile;
JOCTET *buffer;
bool start_of_file;
};
void jpeg_corefile_source::source(j_decompress_ptr cinfo, util::core_file &file)
{
jpeg_corefile_source *src;
if (!cinfo->src)
{
src = reinterpret_cast<jpeg_corefile_source *>(
(*cinfo->mem->alloc_small)(
reinterpret_cast<j_common_ptr>(cinfo),
JPOOL_PERMANENT,
sizeof(jpeg_corefile_source)));
cinfo->src = src;
src->buffer = reinterpret_cast<JOCTET *>(
(*cinfo->mem->alloc_small)(
reinterpret_cast<j_common_ptr>(cinfo),
JPOOL_PERMANENT,
INPUT_BUF_SIZE * sizeof(JOCTET)));
}
else
{
src = static_cast<jpeg_corefile_source *>(cinfo->src);
}
src->init_source = &jpeg_corefile_source::do_init;
src->fill_input_buffer = &jpeg_corefile_source::do_fill;
src->skip_input_data = &jpeg_corefile_source::do_skip;
src->resync_to_restart = jpeg_resync_to_restart;
src->term_source = &jpeg_corefile_source::do_term;
src->infile = &file;
src->bytes_in_buffer = 0;
src->next_input_byte = nullptr;
}
} // anonymous namespace
/***************************************************************************
@ -23,7 +123,7 @@
/* utilities */
static void resample_argb_bitmap_average(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy);
static void resample_argb_bitmap_bilinear(u32 *dest, u32 drowpixels, u32 dwidth, u32 dheight, const u32 *source, u32 srowpixels, u32 swidth, u32 sheight, const render_color &color, u32 dx, u32 dy);
static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const png_info &png);
static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const util::png_info &png);
@ -543,81 +643,103 @@ void render_line_to_quad(const render_bounds *bounds, float width, float length_
/*-------------------------------------------------
render_load_jpeg - load a JPG file into a
bitmap
render_load_msdib - load a Microsoft DIB file
into a bitmap
-------------------------------------------------*/
void render_load_jpeg(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename)
void render_load_msdib(bitmap_argb32 &bitmap, util::core_file &file)
{
// deallocate previous bitmap
bitmap.reset();
// define file's full name
std::string fname;
// read the DIB data
util::msdib_error const result = util::msdib_read_bitmap(file, bitmap);
if (result != util::msdib_error::NONE)
{
osd_printf_error("Error reading Microsoft DIB file\n");
bitmap.reset();
}
}
if (dirname == nullptr)
fname = filename;
else
fname.assign(dirname).append(PATH_SEPARATOR).append(filename);
if (file.open(fname) != osd_file::error::NONE)
return;
/*-------------------------------------------------
render_load_jpeg - load a JPEG file into a
bitmap
-------------------------------------------------*/
// define standard JPEG structures
void render_load_jpeg(bitmap_argb32 &bitmap, util::core_file &file)
{
// deallocate previous bitmap
bitmap.reset();
// create a JPEG source for the file
jpeg_decompress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jpeg_create_decompress(&cinfo);
// allocates a buffer for the image
u32 jpg_size = file.size();
std::unique_ptr<unsigned char[]> jpg_buffer = std::make_unique<unsigned char[]>(jpg_size);
// read data from the file and set them in the buffer
file.read(jpg_buffer.get(), jpg_size);
jpeg_mem_src(&cinfo, jpg_buffer.get(), jpg_size);
// read JPEG header and start decompression
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
// allocates the destination bitmap
int w = cinfo.output_width;
int h = cinfo.output_height;
int s = cinfo.output_components;
bitmap.allocate(w, h);
// allocates a buffer to receive the information and copy them into the bitmap
int row_stride = cinfo.output_width * cinfo.output_components;
JSAMPARRAY buffer = (JSAMPARRAY)malloc(sizeof(JSAMPROW));
buffer[0] = (JSAMPROW)malloc(sizeof(JSAMPLE) * row_stride);
while ( cinfo.output_scanline < cinfo.output_height )
jerr.error_exit = [] (j_common_ptr cinfo) { throw cinfo->err; };
JSAMPARRAY buffer = nullptr;
try
{
int j = cinfo.output_scanline;
jpeg_read_scanlines(&cinfo, buffer, 1);
jpeg_create_decompress(&cinfo);
cinfo.mem->max_memory_to_use = 128 * 1024 * 1024;
jpeg_corefile_source::source(&cinfo, file);
if (s == 1)
for (int i = 0; i < w; ++i)
bitmap.pix(j, i) = rgb_t(0xFF, buffer[0][i], buffer[0][i], buffer[0][i]);
// read JPEG header and start decompression
jpeg_read_header(&cinfo, TRUE);
jpeg_start_decompress(&cinfo);
else if (s == 3)
for (int i = 0; i < w; ++i)
bitmap.pix(j, i) = rgb_t(0xFF, buffer[0][i * s], buffer[0][i * s + 1], buffer[0][i * s + 2]);
else
// allocates the destination bitmap
int w = cinfo.output_width;
int h = cinfo.output_height;
int s = cinfo.output_components;
bitmap.allocate(w, h);
// allocates a buffer to receive the information and copy them into the bitmap
int row_stride = cinfo.output_width * cinfo.output_components;
JSAMPARRAY buffer = reinterpret_cast<JSAMPARRAY>(malloc(sizeof(JSAMPROW)));
buffer[0] = reinterpret_cast<JSAMPROW>(malloc(sizeof(JSAMPLE) * row_stride));
while (cinfo.output_scanline < cinfo.output_height)
{
osd_printf_error("Cannot read JPEG data from %s file.\n", fname);
bitmap.reset();
break;
}
}
int j = cinfo.output_scanline;
jpeg_read_scanlines(&cinfo, buffer, 1);
// finish decompression and frees the memory
jpeg_finish_decompress(&cinfo);
if (s == 1)
{
for (int i = 0; i < w; ++i)
bitmap.pix(j, i) = rgb_t(0xFF, buffer[0][i], buffer[0][i], buffer[0][i]);
}
else if (s == 3)
{
for (int i = 0; i < w; ++i)
bitmap.pix(j, i) = rgb_t(0xFF, buffer[0][i * s], buffer[0][i * s + 1], buffer[0][i * s + 2]);
}
else
{
osd_printf_error("Cannot read JPEG data from file.\n");
bitmap.reset();
break;
}
}
// finish decompression and frees the memory
jpeg_finish_decompress(&cinfo);
}
catch (jpeg_error_mgr *err)
{
char msg[1024];
(cinfo.err->format_message)(reinterpret_cast<j_common_ptr>(&cinfo), msg);
osd_printf_error("JPEG error reading data from file: %s\n", msg);
bitmap.reset();
}
jpeg_destroy_decompress(&cinfo);
file.close();
free(buffer[0]);
free(buffer);
if (buffer)
{
if (buffer[0])
free(buffer[0]);
free(buffer);
}
}
@ -626,36 +748,25 @@ void render_load_jpeg(bitmap_argb32 &bitmap, emu_file &file, const char *dirname
bitmap
-------------------------------------------------*/
bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename, bool load_as_alpha_to_existing)
bool render_load_png(bitmap_argb32 &bitmap, util::core_file &file, bool load_as_alpha_to_existing)
{
// deallocate if we're not overlaying alpha
if (!load_as_alpha_to_existing)
bitmap.reset();
// open the file
std::string fname;
if (dirname)
fname.assign(dirname).append(PATH_SEPARATOR).append(filename);
else
fname.assign(filename);
osd_file::error const filerr = file.open(fname);
if (filerr != osd_file::error::NONE)
return false;
// read the PNG data
png_info png;
png_error const result = png.read_file(file);
file.close();
if (result != PNGERR_NONE)
util::png_info png;
util::png_error const result = png.read_file(file);
if (result != util::png_error::NONE)
{
osd_printf_error("%s: Error reading PNG file\n", filename);
osd_printf_error("Error reading PNG file\n");
return false;
}
// if less than 8 bits, upsample
if (PNGERR_NONE != png.expand_buffer_8bit())
if (util::png_error::NONE != png.expand_buffer_8bit())
{
osd_printf_error("%s: Error upsampling PNG bitmap\n", filename);
osd_printf_error("Error upsampling PNG bitmap\n");
return false;
}
@ -663,9 +774,9 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname,
if (!load_as_alpha_to_existing)
{
// non-alpha case
if (PNGERR_NONE != png.copy_to_bitmap(bitmap, hasalpha))
if (util::png_error::NONE != png.copy_to_bitmap(bitmap, hasalpha))
{
osd_printf_error("%s: Error copying PNG bitmap to MAME bitmap\n", filename);
osd_printf_error("Error copying PNG bitmap to MAME bitmap\n");
return false;
}
}
@ -674,7 +785,7 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname,
// verify we can handle this PNG
if (png.bit_depth > 8)
{
osd_printf_error("%s: Unsupported bit depth %d (8 bit max)\n", filename, png.bit_depth);
osd_printf_error("Unsupported bit depth %d (8 bit max)\n", png.bit_depth);
return false;
}
@ -692,7 +803,7 @@ bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname,
to the alpha channel of a bitmap
-------------------------------------------------*/
static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const png_info &png)
static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const util::png_info &png)
{
// FIXME: this function is basically copy/pasted from the PNG code in util, and should be unified with it
u8 accumalpha = 0xff;
@ -803,29 +914,41 @@ static bool copy_png_alpha_to_bitmap(bitmap_argb32 &bitmap, const png_info &png)
render_detect_image - detect image format
-------------------------------------------------*/
ru_imgformat render_detect_image(emu_file &file, const char *dirname, const char *filename)
ru_imgformat render_detect_image(util::core_file &file)
{
// open the file
std::string fname;
if (dirname)
fname.assign(dirname).append(PATH_SEPARATOR).append(filename);
else
fname.assign(filename);
osd_file::error const filerr = file.open(fname);
if (filerr != osd_file::error::NONE)
return RENDUTIL_IMGFORMAT_ERROR;
// PNG: check for valid header
png_error const result = png_info::verify_header(file);
if (result == PNGERR_NONE)
{
file.close();
return RENDUTIL_IMGFORMAT_PNG;
}
util::png_error const png = util::png_info::verify_header(file);
file.seek(0, SEEK_SET);
// TODO: add more when needed
if (util::png_error::NONE == png)
return RENDUTIL_IMGFORMAT_PNG;
file.close();
// JPEG: attempt to read header with libjpeg
jpeg_decompress_struct cinfo;
jpeg_error_mgr jerr;
cinfo.err = jpeg_std_error(&jerr);
jerr.error_exit = [] (j_common_ptr cinfo) { throw cinfo->err; };
try
{
jpeg_create_decompress(&cinfo);
cinfo.mem->max_memory_to_use = 128 * 1024 * 1024;
jpeg_corefile_source::source(&cinfo, file);
jpeg_read_header(&cinfo, TRUE);
jpeg_destroy_decompress(&cinfo);
file.seek(0, SEEK_SET);
return RENDUTIL_IMGFORMAT_JPEG;
}
catch (jpeg_error_mgr *err)
{
jpeg_destroy_decompress(&cinfo);
file.seek(0, SEEK_SET);
}
// Microsoft DIB: check for valid header
util::msdib_error const msdib = util::msdib_verify_header(file);
file.seek(0, SEEK_SET);
if (util::msdib_error::NONE == msdib)
return RENDUTIL_IMGFORMAT_MSDIB;
// TODO: add more as necessary
return RENDUTIL_IMGFORMAT_UNKNOWN;
}

View File

@ -13,7 +13,7 @@
#pragma once
#include "render.h"
#include "rendertypes.h"
#include <cmath>
@ -23,6 +23,8 @@
enum ru_imgformat
{
RENDUTIL_IMGFORMAT_PNG,
RENDUTIL_IMGFORMAT_JPEG,
RENDUTIL_IMGFORMAT_MSDIB,
RENDUTIL_IMGFORMAT_UNKNOWN,
RENDUTIL_IMGFORMAT_ERROR
@ -40,9 +42,10 @@ void render_resample_argb_bitmap_hq(bitmap_argb32 &dest, bitmap_argb32 &source,
bool render_clip_line(render_bounds *bounds, const render_bounds *clip);
bool render_clip_quad(render_bounds *bounds, const render_bounds *clip, render_quad_texuv *texcoords);
void render_line_to_quad(const render_bounds *bounds, float width, float length_extension, render_bounds *bounds0, render_bounds *bounds1);
void render_load_jpeg(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename);
bool render_load_png(bitmap_argb32 &bitmap, emu_file &file, const char *dirname, const char *filename, bool load_as_alpha_to_existing = false);
ru_imgformat render_detect_image(emu_file &file, const char *dirname, const char *filename);
void render_load_msdib(bitmap_argb32 &bitmap, util::core_file &file);
void render_load_jpeg(bitmap_argb32 &bitmap, util::core_file &file);
bool render_load_png(bitmap_argb32 &bitmap, util::core_file &file, bool load_as_alpha_to_existing = false);
ru_imgformat render_detect_image(util::core_file &file);
@ -61,75 +64,6 @@ static inline float render_round_nearest(float f)
}
/*-------------------------------------------------
set_render_bounds_xy - cleaner way to set the
bounds
-------------------------------------------------*/
static inline void set_render_bounds_xy(render_bounds &bounds, float x0, float y0, float x1, float y1)
{
bounds.x0 = x0;
bounds.y0 = y0;
bounds.x1 = x1;
bounds.y1 = y1;
}
/*-------------------------------------------------
set_render_bounds_wh - cleaner way to set the
bounds
-------------------------------------------------*/
static inline void set_render_bounds_wh(render_bounds &bounds, float x0, float y0, float width, float height)
{
bounds.x0 = x0;
bounds.y0 = y0;
bounds.x1 = x0 + width;
bounds.y1 = y0 + height;
}
/*-------------------------------------------------
sect_render_bounds - compute the intersection
of two render_bounds
-------------------------------------------------*/
static inline void sect_render_bounds(render_bounds &dest, const render_bounds &src)
{
dest.x0 = (std::max)(dest.x0, src.x0);
dest.x1 = (std::min)(dest.x1, src.x1);
dest.y0 = (std::max)(dest.y0, src.y0);
dest.y1 = (std::min)(dest.y1, src.y1);
}
/*-------------------------------------------------
union_render_bounds - compute the union of two
render_bounds
-------------------------------------------------*/
static inline void union_render_bounds(render_bounds &dest, const render_bounds &src)
{
dest.x0 = (std::min)(dest.x0, src.x0);
dest.x1 = (std::max)(dest.x1, src.x1);
dest.y0 = (std::min)(dest.y0, src.y0);
dest.y1 = (std::max)(dest.y1, src.y1);
}
/*-------------------------------------------------
set_render_color - cleaner way to set a color
-------------------------------------------------*/
static inline void set_render_color(render_color *color, float a, float r, float g, float b)
{
color->a = a;
color->r = r;
color->g = g;
color->b = b;
}
/*-------------------------------------------------
orientation_swap_flips - swap the X and Y
flip flags
@ -213,5 +147,4 @@ static inline u8 apply_brightness_contrast_gamma(u8 src, float brightness, float
return u8(result * 255.0f);
}
#endif // MAME_EMU_RENDUTIL_H

View File

@ -9,14 +9,15 @@
***************************************************************************/
#include "emu.h"
#include "screen.h"
#include "emuopts.h"
#include "png.h"
#include "render.h"
#include "rendutil.h"
#include <nanosvg/src/nanosvg.h>
#include <nanosvg/src/nanosvgrast.h>
#include "nanosvg.h"
#include "png.h"
#include <clocale>
#include <set>
@ -43,7 +44,6 @@ u32 screen_device::m_id_counter = 0;
class screen_device::svg_renderer {
public:
svg_renderer(memory_region *region);
~svg_renderer();
int width() const;
int height() const;
@ -69,8 +69,8 @@ private:
int x0, y0, x1, y1;
};
NSVGimage *m_image;
NSVGrasterizer *m_rasterizer;
util::nsvg_image_ptr m_image;
util::nsvg_rasterizer_ptr m_rasterizer;
std::vector<bool> m_key_state;
std::vector<std::vector<NSVGshape *>> m_keyed_shapes;
std::unordered_map<std::string, int> m_key_ids;
@ -97,8 +97,8 @@ screen_device::svg_renderer::svg_renderer(memory_region *region)
const std::unique_ptr<char []> s(new char[region->bytes() + 1]);
memcpy(s.get(), region->base(), region->bytes());
s[region->bytes()] = 0;
m_image = nsvgParse(s.get(), "px", 72);
m_rasterizer = nsvgCreateRasterizer();
m_image.reset(nsvgParse(s.get(), "px", 72));
m_rasterizer.reset(nsvgCreateRasterizer());
m_key_count = 0;
@ -123,12 +123,6 @@ screen_device::svg_renderer::svg_renderer(memory_region *region)
osd_printf_verbose("Parsed SVG '%s', aspect ratio %f\n", region->name(), (m_image->height == 0.0f) ? 0 : m_image->width / m_image->height);
}
screen_device::svg_renderer::~svg_renderer()
{
nsvgDeleteRasterizer(m_rasterizer);
nsvgDelete(m_image);
}
int screen_device::svg_renderer::width() const
{
return int(m_image->width + 0.5);
@ -150,7 +144,7 @@ void screen_device::svg_renderer::render_state(std::vector<u32> &dest, const std
s->flags &= ~NSVG_FLAGS_VISIBLE;
}
nsvgRasterize(m_rasterizer, m_image, 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4);
nsvgRasterize(m_rasterizer.get(), m_image.get(), 0, 0, m_scale, (unsigned char *)&dest[0], m_sx, m_sy, m_sx*4);
// Nanosvg generates non-premultiplied alpha, so remultiply by
// alpha to "blend" against a black background. Plus align the
@ -1921,14 +1915,14 @@ void screen_device::finalize_burnin()
osd_file::error filerr = file.open(util::string_format("%s" PATH_SEPARATOR "burnin-%s.png", machine().basename(), tag() + 1));
if (filerr == osd_file::error::NONE)
{
png_info pnginfo;
util::png_info pnginfo;
// add two text entries describing the image
pnginfo.add_text("Software", util::string_format("%s %s", emulator_info::get_appname(), emulator_info::get_build_version()).c_str());
pnginfo.add_text("System", util::string_format("%s %s", machine().system().manufacturer, machine().system().type.fullname()).c_str());
// now do the actual work
png_write_bitmap(file, &pnginfo, finalmap, 0, nullptr);
util::png_write_bitmap(file, &pnginfo, finalmap, 0, nullptr);
}
}
@ -1947,8 +1941,13 @@ void screen_device::load_effect_overlay(const char *filename)
fullname.append(".png");
// load the file
m_screen_overlay_bitmap.reset();
emu_file file(machine().options().art_path(), OPEN_FLAG_READ);
render_load_png(m_screen_overlay_bitmap, file, nullptr, fullname.c_str());
if (file.open(fullname) == osd_file::error::NONE)
{
render_load_png(m_screen_overlay_bitmap, file);
file.close();
}
if (m_screen_overlay_bitmap.valid())
m_container->set_overlay(&m_screen_overlay_bitmap);
else

View File

@ -329,16 +329,16 @@ void video_manager::save_snapshot(screen_device *screen, emu_file &file)
// add two text entries describing the image
std::string text1 = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version());
std::string text2 = std::string(machine().system().manufacturer).append(" ").append(machine().system().type.fullname());
png_info pnginfo;
util::png_info pnginfo;
pnginfo.add_text("Software", text1.c_str());
pnginfo.add_text("System", text2.c_str());
// now do the actual work
const rgb_t *palette = (screen != nullptr && screen->has_palette()) ? screen->palette().palette()->entry_list_adjusted() : nullptr;
int entries = (screen != nullptr && screen->has_palette()) ? screen->palette().entries() : 0;
png_error error = png_write_bitmap(file, &pnginfo, m_snap_bitmap, entries, palette);
if (error != PNGERR_NONE)
osd_printf_error("Error generating PNG for snapshot: png_error = %d\n", error);
util::png_error error = util::png_write_bitmap(file, &pnginfo, m_snap_bitmap, entries, palette);
if (error != util::png_error::NONE)
osd_printf_error("Error generating PNG for snapshot: png_error = %d\n", std::underlying_type_t<util::png_error>(error));
}

View File

@ -19,6 +19,7 @@
#include "emu.h"
#include "icorender.h"
#include "util/msdib.h"
#include "util/png.h"
#include <algorithm>
@ -26,11 +27,10 @@
#include <cstdint>
#include <cstring>
// need to set LOG_OUTPUT_STREAM because there's no logerror outside devices
#define LOG_OUTPUT_STREAM std::cerr
// need to set LOG_OUTPUT_FUNC or LOG_OUTPUT_STREAM because there's no logerror outside devices
#define LOG_OUTPUT_FUNC osd_printf_verbose
#define LOG_GENERAL (1U << 0)
#define LOG_DIB (1U << 1)
//#define VERBOSE (LOG_GENERAL | LOG_DIB)
@ -41,16 +41,6 @@ namespace ui {
namespace {
// DIB compression schemes
enum : uint32_t
{
DIB_COMP_NONE = 0,
DIB_COMP_RLE8 = 1,
DIB_COMP_RLE4 = 2,
DIB_COMP_BITFIELDS = 3
};
// ICO file header
struct icon_dir_t
{
@ -83,67 +73,6 @@ struct icon_dir_entry_t
uint32_t offset; // offset to image data from start of file
};
// old-style DIB header
struct bitmap_core_header_t
{
uint32_t size; // size of the header (12, 16 or 64)
int16_t width; // width of bitmap in pixels
int16_t height; // height of the image in pixels
uint16_t planes; // number of colour planes (must be 1)
uint16_t bpp; // bits per pixel
};
// new-style DIB header
struct bitmap_info_header_t
{
uint32_t size; // size of the header
int32_t width; // width of bitmap in pixels
int32_t height; // height of bitmap in pixels
uint16_t planes; // number of colour planes (must be 1)
uint16_t bpp; // bits per pixel
uint32_t comp; // compression method
uint32_t rawsize; // size of bitmap data after decompression or 0 if uncompressed
int32_t hres; // horizontal resolution in pixels/metre
int32_t vres; // horizontal resolution in pixels/metre
uint32_t colors; // number of colours or 0 for 1 << bpp
uint32_t important; // number of important colours or 0 if all important
uint32_t red; // red field mask - must be contiguous
uint32_t green; // green field mask - must be contiguous
uint32_t blue; // blue field mask - must be contiguous
uint32_t alpha; // alpha field mask - must be contiguous
};
bool dib_parse_mask(uint32_t mask, unsigned &shift, unsigned &bits)
{
shift = count_leading_zeros(mask);
mask <<= shift;
bits = count_leading_ones(mask);
mask <<= shift;
shift = 32 - shift - bits;
return !mask;
}
void dib_truncate_channel(unsigned &shift, unsigned &bits)
{
if (8U < bits)
{
unsigned const excess(bits - 8);
shift += excess;
bits -= excess;
}
}
uint8_t dib_splat_sample(uint8_t val, unsigned bits)
{
assert(8U >= bits);
for (val <<= (8U - bits); bits && (8U > bits); bits <<= 1)
val |= val >> bits;
return val;
}
bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap)
{
@ -151,10 +80,10 @@ bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb3
if (9U >= dir.size)
return false;
fp.seek(dir.offset, SEEK_SET);
png_error const err(png_read_bitmap(fp, bitmap));
util::png_error const err(util::png_read_bitmap(fp, bitmap));
switch (err)
{
case PNGERR_NONE:
case util::png_error::NONE:
// found valid PNG image
assert(bitmap.valid());
if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height())))
@ -172,7 +101,7 @@ bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb3
}
return true;
case PNGERR_BAD_SIGNATURE:
case util::png_error::BAD_SIGNATURE:
// doesn't look like PNG data - just fall back to DIB without the file header
return false;
@ -190,426 +119,37 @@ bool load_ico_png(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb3
bool load_ico_dib(util::core_file &fp, icon_dir_entry_t const &dir, bitmap_argb32 &bitmap)
{
// check that these things haven't been padded somehow
static_assert(sizeof(bitmap_core_header_t) == 12U, "compiler has applied padding to bitmap_core_header_t");
static_assert(sizeof(bitmap_info_header_t) == 56U, "compiler has applied padding to bitmap_info_header_t");
// ensure the header fits in the space for the image data
union { bitmap_core_header_t core; bitmap_info_header_t info; } header;
assert(&header.core.size == &header.info.size);
if (sizeof(header.core) > dir.size)
return false;
std::memset(&header, 0, sizeof(header));
fp.seek(dir.offset, SEEK_SET);
if (fp.read(&header.core.size, sizeof(header.core.size)) != sizeof(header.core.size))
util::msdib_error const err(util::msdib_read_bitmap_data(fp, bitmap, dir.size, dir.get_height()));
switch (err)
{
case util::msdib_error::NONE:
// found valid DIB image
assert(bitmap.valid());
if ((dir.get_width() == bitmap.width()) && ((dir.get_height() == bitmap.height())))
{
LOG("Loaded %d*%d pixel DIB image from ICO file\n", bitmap.width(), bitmap.height());
}
else
{
LOG(
"Loaded %d*%d pixel DIB image from ICO file (directory indicated %u*%u)\n",
bitmap.width(),
bitmap.height(),
dir.get_width(),
dir.get_height());
}
return true;
default:
// invalid DIB data or I/O error
LOG(
"Error reading DIB header size from ICO file at offset %u (directory size %u)\n",
"Error %u reading DIB image data from ICO file at offset %u (directory size %u)\n",
unsigned(err),
dir.offset,
dir.size);
return false;
}
header.core.size = little_endianize_int32(header.core.size);
if (dir.size < header.core.size)
{
LOG(
"ICO file image data at %u (%u bytes) is too small for DIB header (%u bytes)\n",
dir.offset,
dir.size,
header.core.size);
return false;
}
// identify and read the header - convert OS/2 headers to Windows 3 format
unsigned palette_bytes(4U);
switch (header.core.size)
{
case 16U:
case 64U:
// extended OS/2 bitmap header with support for compression
LOG(
"ICO image data at %u (%u bytes) uses unsupported OS/2 DIB header (size %u)\n",
dir.offset,
dir.size,
header.core.size);
return false;
case 12U:
// introduced in OS/2 and Windows 2.0
{
palette_bytes = 3U;
uint32_t const header_read(std::min<uint32_t>(header.core.size, sizeof(header.core)) - sizeof(header.core.size));
if (fp.read(&header.core.width, header_read) != header_read)
{
LOG("Error reading DIB core header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size);
return false;
}
fp.seek(header.core.size - sizeof(header.core.size) - header_read, SEEK_CUR);
header.core.width = little_endianize_int16(header.core.width);
header.core.height = little_endianize_int16(header.core.height);
header.core.planes = little_endianize_int16(header.core.planes);
header.core.bpp = little_endianize_int16(header.core.bpp);
LOGMASKED(
LOG_DIB,
"Read DIB core header from ICO file image data at %u: %d*%d, %u planes, %u bpp\n",
dir.offset,
header.core.width,
header.core.height,
header.core.planes,
header.core.bpp);
// this works because the core header only aliases the width/height of the info header
header.info.bpp = header.core.bpp;
header.info.planes = header.core.planes;
header.info.height = header.core.height;
header.info.width = header.core.width;
header.info.size = 40U;
}
break;
default:
// the next version will be longer
if (124U >= header.core.size)
{
LOG(
"ICO image data at %u (%u bytes) uses unsupported DIB header format (size %u)\n",
dir.offset,
dir.size,
header.core.size);
return false;
}
// fall through
case 40U:
case 52U:
case 56U:
case 108U:
case 124U:
// the Windows 3 bitmap header with optional extensions
{
palette_bytes = 4U;
uint32_t const header_read(std::min<uint32_t>(header.info.size, sizeof(header.info)) - sizeof(header.info.size));
if (fp.read(&header.info.width, header_read) != header_read)
{
LOG("Error reading DIB info header from ICO file image data at %u (%u bytes)\n", dir.offset, dir.size);
return false;
}
fp.seek(header.info.size - sizeof(header.info.size) - header_read, SEEK_CUR);
header.info.width = little_endianize_int32(header.info.width);
header.info.height = little_endianize_int32(header.info.height);
header.info.planes = little_endianize_int16(header.info.planes);
header.info.bpp = little_endianize_int16(header.info.bpp);
header.info.comp = little_endianize_int32(header.info.comp);
header.info.rawsize = little_endianize_int32(header.info.rawsize);
header.info.hres = little_endianize_int32(header.info.hres);
header.info.vres = little_endianize_int32(header.info.vres);
header.info.colors = little_endianize_int32(header.info.colors);
header.info.important = little_endianize_int32(header.info.important);
header.info.red = little_endianize_int32(header.info.red);
header.info.green = little_endianize_int32(header.info.green);
header.info.blue = little_endianize_int32(header.info.blue);
header.info.alpha = little_endianize_int32(header.info.alpha);
LOGMASKED(
LOG_DIB,
"Read DIB info header from ICO file image data at %u: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n",
dir.offset,
header.info.width,
header.info.height,
header.info.hres,
header.info.vres,
header.info.planes,
header.info.bpp,
header.info.important,
header.info.colors ? "" : "2^",
header.info.colors ? header.info.colors : header.info.bpp);
}
break;
}
// check for unsupported planes/bit depth
if ((1U != header.info.planes) || !header.info.bpp || (32U < header.info.bpp) || ((8U < header.info.bpp) ? (header.info.bpp % 8) : (8 % header.info.bpp)))
{
LOG(
"ICO file DIB image data at %u uses unsupported planes/bits per pixel %u*%u\n",
dir.offset,
header.info.planes,
header.info.bpp);
return false;
}
// check dimensions
if ((0 >= header.info.width) || (0 == header.info.height))
{
LOG(
"ICO file DIB image data at %u has invalid dimensions %u*%u\n",
dir.offset,
header.info.width,
header.info.height);
return false;
}
bool const top_down(0 > header.info.height);
if (top_down)
header.info.height = -header.info.height;
bool have_and_mask((2 * dir.get_height()) == header.info.height);
if (!have_and_mask && (dir.get_height() != header.info.height))
{
osd_printf_verbose(
"ICO file DIB image data at %lu height %ld doesn't match directory height %u with or without AND mask\n",
(unsigned long)dir.offset,
(long)header.info.height,
dir.get_height());
return false;
}
if (have_and_mask)
header.info.height >>= 1;
// ensure compression scheme is supported
bool indexed(true), no_palette(false);
switch (header.info.comp)
{
case DIB_COMP_NONE:
// uncompressed - direct colour with implied bitfields if more than eight bits/pixel
indexed = 8U >= header.info.bpp;
if (indexed)
{
if ((1U << header.info.bpp) < header.info.colors)
{
osd_printf_verbose(
"ICO file DIB image data at %lu has oversized palette with %lu entries for %u bits per pixel\n",
(unsigned long)dir.offset,
(unsigned long)header.info.colors,
(unsigned)header.info.bpp);
}
}
if (!indexed)
{
no_palette = true;
switch(header.info.bpp)
{
case 16U:
header.info.red = 0x00007c00;
header.info.green = 0x000003e0;
header.info.blue = 0x0000001f;
header.info.alpha = 0x00000000;
break;
case 24U:
case 32U:
header.info.red = 0x00ff0000;
header.info.green = 0x0000ff00;
header.info.blue = 0x000000ff;
header.info.alpha = 0x00000000;
break;
}
}
break;
case DIB_COMP_BITFIELDS:
// uncompressed direct colour with explicitly-specified bitfields
indexed = false;
if (offsetof(bitmap_info_header_t, alpha) > header.info.size)
{
osd_printf_verbose(
"ICO file DIB image data at %lu specifies bit masks but is too small (size %lu)\n",
(unsigned long)dir.offset,
(unsigned long)header.info.size);
return false;
}
break;
default:
LOG("ICO file DIB image data at %u uses unsupported compression scheme %u\n", header.info.comp);
return false;
}
// we can now calculate the size of the palette and row data
size_t const palette_entries(
indexed
? ((1U == header.info.bpp) ? 2U : header.info.colors ? header.info.colors : (1U << header.info.bpp))
: (no_palette ? 0U : header.info.colors));
size_t const palette_size(palette_bytes * palette_entries);
size_t const row_bytes(((31 + (header.info.width * header.info.bpp)) >> 5) << 2);
size_t const mask_row_bytes(((31 + header.info.width) >> 5) << 2);
size_t const required_size(
header.info.size +
palette_size +
((row_bytes + (have_and_mask ? mask_row_bytes : 0U)) * header.info.height));
if (required_size > dir.size)
{
LOG(
"ICO file image data at %u (%u bytes) smaller than calculated DIB data size (%u bytes)\n",
dir.offset,
dir.size,
required_size);
return false;
}
// load the palette for indexed colour formats or the shifts for direct colour formats
unsigned red_shift(0), green_shift(0), blue_shift(0), alpha_shift(0);
unsigned red_bits(0), green_bits(0), blue_bits(0), alpha_bits(0);
std::unique_ptr<rgb_t []> palette;
if (indexed)
{
// read palette and convert
std::unique_ptr<uint8_t []> palette_data(new uint8_t [palette_size]);
if (fp.read(palette_data.get(), palette_size) != palette_size)
{
LOG("Error reading palette from ICO file DIB image data at %u (%u bytes)\n", dir.offset, dir.size);
return false;
}
size_t const palette_usable(std::min<size_t>(palette_entries, size_t(1) << header.info.bpp));
palette.reset(new rgb_t [palette_usable]);
uint8_t const *ptr(palette_data.get());
for (size_t i = 0; palette_usable > i; ++i, ptr += palette_bytes)
palette[i] = rgb_t(ptr[2], ptr[1], ptr[0]);
}
else
{
// skip over the palette if necessary
if (palette_entries)
fp.seek(palette_bytes * palette_entries, SEEK_CUR);
// convert masks to shifts
bool const masks_contiguous(
dib_parse_mask(header.info.red, red_shift, red_bits) &&
dib_parse_mask(header.info.green, green_shift, green_bits) &&
dib_parse_mask(header.info.blue, blue_shift, blue_bits) &&
dib_parse_mask(header.info.alpha, alpha_shift, alpha_bits));
if (!masks_contiguous)
{
osd_printf_verbose(
"ICO file DIB image data at %lu specifies non-contiguous channel masks 0x%lx | 0x%lx | 0x%lx | 0x%lx\n",
(unsigned long)dir.offset,
(unsigned long)header.info.red,
(unsigned long)header.info.green,
(unsigned long)header.info.blue,
(unsigned long)header.info.alpha);
}
if ((32U != header.info.bpp) && ((header.info.red | header.info.green | header.info.blue | header.info.alpha) >> header.info.bpp))
{
LOG(
"ICO file DIB image data at %lu specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n",
dir.offset,
header.info.red,
header.info.green,
header.info.blue,
header.info.alpha,
header.info.bpp);
return false;
}
LOGMASKED(
LOG_DIB,
"DIB from ICO file image data at %1$u using channels: R((x >> %3$u) & 0x%4$0*2$x) G((x >> %5$u) & 0x%6$0*2$x) B((x >> %7$u) & 0x%8$0*2$x) A((x >> %9$u) & 0x%10$0*2$x)\n",
dir.offset,
(header.info.bpp + 3) >> 2,
red_shift,
(uint32_t(1) << red_bits) - 1,
green_shift,
(uint32_t(1) << green_bits) - 1,
blue_shift,
(uint32_t(1) << blue_bits) - 1,
alpha_shift,
(uint32_t(1) << alpha_bits) - 1);
// the MAME bitmap only supports 8 bits/sample maximum
dib_truncate_channel(red_shift, red_bits);
dib_truncate_channel(green_shift, green_bits);
dib_truncate_channel(blue_shift, blue_bits);
dib_truncate_channel(alpha_shift, alpha_bits);
}
// allocate the bitmap and process row data
std::unique_ptr<uint8_t []> row_data(new uint8_t [row_bytes]);
bitmap.allocate(header.info.width, header.info.height);
int const y_inc(top_down ? 1 : -1);
for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
{
if (fp.read(row_data.get(), row_bytes) != row_bytes)
{
LOG("Error reading DIB row %d data from ICO image data at %u\n", i, dir.offset);
return false;
}
uint8_t *src(row_data.get());
uint32_t *dest(&bitmap.pix(y));
unsigned shift(0U);
for (int32_t x = 0; header.info.width > x; ++x, ++dest)
{
// extract or compose a pixel
uint32_t pix(0U);
if (8U >= header.info.bpp)
{
assert(8U > shift);
pix = *src >> (8U - header.info.bpp);
*src <<= header.info.bpp;
shift += header.info.bpp;
if (8U <= shift)
{
shift = 0U;
++src;
}
}
else for (shift = 0; header.info.bpp > shift; shift += 8U, ++src)
{
pix |= uint32_t(*src) << shift;
}
// convert to RGB
if (indexed)
{
if (palette_entries > pix)
{
*dest = palette[pix];
}
else
{
*dest = rgb_t::transparent();
osd_printf_verbose(
"ICO file DIB image data at %lu has out-of-range color %lu at (%ld, %ld) with %lu palette entries\n",
(unsigned long)dir.offset,
(unsigned long)pix,
(long)x,
(long)y,
(unsigned long)palette_entries);
}
}
else
{
uint8_t r(dib_splat_sample((pix >> red_shift) & ((uint32_t(1) << red_bits) - 1), red_bits));
uint8_t g(dib_splat_sample((pix >> green_shift) & ((uint32_t(1) << green_bits) - 1), green_bits));
uint8_t b(dib_splat_sample((pix >> blue_shift) & ((uint32_t(1) << blue_bits) - 1), blue_bits));
uint8_t a(dib_splat_sample((pix >> alpha_shift) & ((uint32_t(1) << alpha_bits) - 1), alpha_bits));
*dest = rgb_t(alpha_bits ? a : 255, r, g, b);
}
}
}
// process the AND mask if present
if (have_and_mask)
{
for (int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
{
if (fp.read(row_data.get(), mask_row_bytes) != mask_row_bytes)
{
LOG("Error reading DIB mask row %d data from ICO image data at %u\n", i, dir.offset);
return false;
}
uint8_t *src(row_data.get());
uint32_t *dest(&bitmap.pix(y));
unsigned shift(0U);
for (int32_t x = 0; header.info.width > x; ++x, ++dest)
{
assert(8U > shift);
rgb_t pix(*dest);
*dest = pix.set_a(BIT(*src, 7U - shift) ? 0U : pix.a());
if (8U <= ++shift)
{
shift = 0U;
++src;
}
}
}
}
// we're done!
return true;
}

View File

@ -92,10 +92,17 @@ menu::global_state::global_state(running_machine &machine, ui_options const &opt
{
m_bgrnd_bitmap = std::make_unique<bitmap_argb32>(0, 0);
emu_file backgroundfile(".", OPEN_FLAG_READ);
render_load_jpeg(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.jpg");
if (backgroundfile.open("background.jpg") == osd_file::error::NONE)
{
render_load_jpeg(*m_bgrnd_bitmap, backgroundfile);
backgroundfile.close();
}
if (!m_bgrnd_bitmap->valid())
render_load_png(*m_bgrnd_bitmap, backgroundfile, nullptr, "background.png");
if (!m_bgrnd_bitmap->valid() && (backgroundfile.open("background.png") == osd_file::error::NONE))
{
render_load_png(*m_bgrnd_bitmap, backgroundfile);
backgroundfile.close();
}
if (m_bgrnd_bitmap->valid())
m_bgrnd_texture->set_bitmap(*m_bgrnd_bitmap, m_bgrnd_bitmap->cliprect(), TEXFORMAT_ARGB32);

View File

@ -95,6 +95,62 @@ char const *const hover_msg[] = {
__("Show DATs view"),
};
void load_image(bitmap_argb32 &bitmap, emu_file &file, std::string const &base)
{
if (file.open(base + ".png") == osd_file::error::NONE)
{
render_load_png(bitmap, file);
file.close();
}
if (!bitmap.valid() && (file.open(base + ".jpg") == osd_file::error::NONE))
{
render_load_jpeg(bitmap, file);
file.close();
}
if (!bitmap.valid() && (file.open(base + ".bmp") == osd_file::error::NONE))
{
render_load_msdib(bitmap, file);
file.close();
}
}
void load_driver_image(bitmap_argb32 &bitmap, emu_file &file, game_driver const &driver)
{
// try to load snapshot first from saved "0000.png" file
std::string fullname = driver.name;
load_image(bitmap, file, fullname + PATH_SEPARATOR + "0000");
// if fail, attempt to load from standard file
if (!bitmap.valid())
load_image(bitmap, file, fullname);
// if fail again, attempt to load from parent file
if (!bitmap.valid())
{
// ignore BIOS sets
bool isclone = strcmp(driver.parent, "0") != 0;
if (isclone)
{
int const cx = driver_list::find(driver.parent);
if ((0 <= cx) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT))
isclone = false;
}
if (isclone)
{
fullname = driver.parent;
load_image(bitmap, file, fullname + PATH_SEPARATOR + "0000");
if (!bitmap.valid())
load_image(bitmap, file, fullname);
}
}
}
} // anonymous namespace
constexpr std::size_t menu_select_launch::MAX_VISIBLE_SEARCH; // stupid non-inline semantics
@ -2226,41 +2282,16 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f
if (software->startempty == 1)
{
// Load driver snapshot
std::string fullname = std::string(software->driver->name) + ".png";
render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str());
if (!tmp_bitmap.valid())
{
fullname.assign(software->driver->name).append(".jpg");
render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str());
}
load_driver_image(tmp_bitmap, snapfile, *software->driver);
}
else
{
// First attempt from name list
std::string pathname = software->listname;
std::string fullname = software->shortname + ".png";
render_load_png(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str());
load_image(tmp_bitmap, snapfile, software->listname + PATH_SEPARATOR + software->shortname);
// Second attempt from driver name + part name
if (!tmp_bitmap.valid())
{
fullname.assign(software->shortname).append(".jpg");
render_load_jpeg(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str());
}
if (!tmp_bitmap.valid())
{
// Second attempt from driver name + part name
pathname.assign(software->driver->name).append(software->part);
fullname.assign(software->shortname).append(".png");
render_load_png(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str());
if (!tmp_bitmap.valid())
{
fullname.assign(software->shortname).append(".jpg");
render_load_jpeg(tmp_bitmap, snapfile, pathname.c_str(), fullname.c_str());
}
}
load_image(tmp_bitmap, snapfile, software->driver->name + software->part + PATH_SEPARATOR + software->shortname);
}
m_cache->set_snapx_software(software);
@ -2285,51 +2316,7 @@ void menu_select_launch::arts_render(float origx1, float origy1, float origx2, f
{
emu_file snapfile(searchstr, OPEN_FLAG_READ);
bitmap_argb32 tmp_bitmap;
// try to load snapshot first from saved "0000.png" file
std::string fullname(driver->name);
render_load_png(tmp_bitmap, snapfile, fullname.c_str(), "0000.png");
if (!tmp_bitmap.valid())
render_load_jpeg(tmp_bitmap, snapfile, fullname.c_str(), "0000.jpg");
// if fail, attemp to load from standard file
if (!tmp_bitmap.valid())
{
fullname.assign(driver->name).append(".png");
render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str());
if (!tmp_bitmap.valid())
{
fullname.assign(driver->name).append(".jpg");
render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str());
}
}
// if fail again, attemp to load from parent file
if (!tmp_bitmap.valid())
{
// set clone status
bool cloneof = strcmp(driver->parent, "0");
if (cloneof)
{
int cx = driver_list::find(driver->parent);
if ((cx >= 0) && (driver_list::driver(cx).flags & machine_flags::IS_BIOS_ROOT))
cloneof = false;
}
if (cloneof)
{
fullname.assign(driver->parent).append(".png");
render_load_png(tmp_bitmap, snapfile, nullptr, fullname.c_str());
if (!tmp_bitmap.valid())
{
fullname.assign(driver->parent).append(".jpg");
render_load_jpeg(tmp_bitmap, snapfile, nullptr, fullname.c_str());
}
}
}
load_driver_image(tmp_bitmap, snapfile, *driver);
m_cache->set_snapx_driver(driver);
m_switch_image = false;

642
src/lib/util/msdib.cpp Normal file
View File

@ -0,0 +1,642 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
msdib.h
Microsoft Device-Independent Bitmap file loading.
***************************************************************************/
#include "msdib.h"
#include "eminline.h"
#include <cassert>
#include <cstdlib>
#include <cstring>
#define LOG_GENERAL (1U << 0)
#define LOG_DIB (1U << 1)
//#define VERBOSE (LOG_GENERAL | LOG_DIB)
#define LOG_OUTPUT_FUNC osd_printf_verbose
#ifndef VERBOSE
#define VERBOSE 0
#endif
#define LOGMASKED(mask, ...) do { if (VERBOSE & (mask)) (LOG_OUTPUT_FUNC)(__VA_ARGS__); } while (false)
#define LOG(...) LOGMASKED(LOG_GENERAL, __VA_ARGS__)
namespace util {
namespace {
// DIB compression schemes
enum : std::uint32_t
{
DIB_COMP_NONE = 0,
DIB_COMP_RLE8 = 1,
DIB_COMP_RLE4 = 2,
DIB_COMP_BITFIELDS = 3
};
// file header doesn't use natural alignment
using bitmap_file_header = std::uint8_t [14];
// old-style DIB header
struct bitmap_core_header
{
std::uint32_t size; // size of the header (12, 16 or 64)
std::int16_t width; // width of bitmap in pixels
std::int16_t height; // height of the image in pixels
std::uint16_t planes; // number of colour planes (must be 1)
std::uint16_t bpp; // bits per pixel
};
// new-style DIB header
struct bitmap_info_header
{
std::uint32_t size; // size of the header
std::int32_t width; // width of bitmap in pixels
std::int32_t height; // height of bitmap in pixels
std::uint16_t planes; // number of colour planes (must be 1)
std::uint16_t bpp; // bits per pixel
std::uint32_t comp; // compression method
std::uint32_t rawsize; // size of bitmap data after decompression or 0 if uncompressed
std::int32_t hres; // horizontal resolution in pixels/metre
std::int32_t vres; // horizontal resolution in pixels/metre
std::uint32_t colors; // number of colours or 0 for 1 << bpp
std::uint32_t important; // number of important colours or 0 if all important
std::uint32_t red; // red field mask - must be contiguous
std::uint32_t green; // green field mask - must be contiguous
std::uint32_t blue; // blue field mask - must be contiguous
std::uint32_t alpha; // alpha field mask - must be contiguous
};
union bitmap_headers
{
bitmap_core_header core;
bitmap_info_header info;
};
bool dib_parse_mask(uint32_t mask, unsigned &shift, unsigned &bits)
{
shift = count_leading_zeros(mask);
mask <<= shift;
bits = count_leading_ones(mask);
mask <<= shift;
shift = 32 - shift - bits;
return !mask;
}
void dib_truncate_channel(unsigned &shift, unsigned &bits)
{
if (8U < bits)
{
unsigned const excess(bits - 8);
shift += excess;
bits -= excess;
}
}
uint8_t dib_splat_sample(uint8_t val, unsigned bits)
{
assert(8U >= bits);
for (val <<= (8U - bits); bits && (8U > bits); bits <<= 1)
val |= val >> bits;
return val;
}
msdib_error dib_read_file_header(core_file &fp, std::uint32_t &filelen)
{
// the bitmap file header doesn't use natural alignment
bitmap_file_header file_header;
if (fp.read(file_header, sizeof(file_header)) != sizeof(file_header))
{
LOG("Error reading DIB file header\n");
return msdib_error::FILE_TRUNCATED;
}
// only support Windows bitmaps for now
if ((0x42 != file_header[0]) || (0x4d != file_header[1]))
return msdib_error::BAD_SIGNATURE;
// do a very basic check on the file length
std::uint32_t const file_length(
(std::uint32_t(file_header[2]) << 0) |
(std::uint32_t(file_header[3]) << 8) |
(std::uint32_t(file_header[4]) << 16) |
(std::uint32_t(file_header[5]) << 24));
if ((sizeof(file_header) + sizeof(bitmap_core_header)) > file_length)
return msdib_error::FILE_CORRUPT;
// check that the offset to the pixel data looks half sane
std::uint32_t const pixel_offset(
(std::uint32_t(file_header[10]) << 0) |
(std::uint32_t(file_header[11]) << 8) |
(std::uint32_t(file_header[12]) << 16) |
(std::uint32_t(file_header[13]) << 24));
if (((sizeof(file_header) + sizeof(bitmap_core_header)) > pixel_offset) || (file_length < pixel_offset))
return msdib_error::FILE_CORRUPT;
// looks OK enough
filelen = file_length;
return msdib_error::NONE;
}
msdib_error dib_read_bitmap_header(
core_file &fp,
bitmap_headers &header,
unsigned &palette_bytes,
bool &indexed,
std::size_t &palette_entries,
std::size_t &palette_size,
std::size_t &row_bytes,
std::uint32_t length)
{
// check that these things haven't been padded somehow
static_assert(sizeof(bitmap_core_header) == 12U, "compiler has applied padding to bitmap_core_header");
static_assert(sizeof(bitmap_info_header) == 56U, "compiler has applied padding to bitmap_info_header");
// ensure the header fits in the space for the image data
assert(&header.core.size == &header.info.size);
if (sizeof(header.core) > length)
return msdib_error::FILE_TRUNCATED;
std::memset(&header, 0, sizeof(header));
if (fp.read(&header.core.size, sizeof(header.core.size)) != sizeof(header.core.size))
{
LOG("Error reading DIB header size (length %u)\n", length);
return msdib_error::FILE_TRUNCATED;
}
header.core.size = little_endianize_int32(header.core.size);
if (length < header.core.size)
{
LOG("DIB image data (%u bytes) is too small for DIB header (%u bytes)\n", length, header.core.size);
return msdib_error::FILE_CORRUPT;
}
// identify and read the header - convert OS/2 headers to Windows 3 format
palette_bytes = 4U;
switch (header.core.size)
{
case 16U:
case 64U:
// extended OS/2 bitmap header with support for compression
LOG(
"DIB image data (%u bytes) uses unsupported OS/2 DIB header (size %u)\n",
length,
header.core.size);
return msdib_error::UNSUPPORTED_FORMAT;
case 12U:
// introduced in OS/2 and Windows 2.0
{
palette_bytes = 3U;
std::uint32_t const header_read(std::min<std::uint32_t>(header.core.size, sizeof(header.core)) - sizeof(header.core.size));
if (fp.read(&header.core.width, header_read) != header_read)
{
LOG("Error reading DIB core header from image data (%u bytes)\n", length);
return msdib_error::FILE_TRUNCATED;
}
fp.seek(header.core.size - sizeof(header.core.size) - header_read, SEEK_CUR);
header.core.width = little_endianize_int16(header.core.width);
header.core.height = little_endianize_int16(header.core.height);
header.core.planes = little_endianize_int16(header.core.planes);
header.core.bpp = little_endianize_int16(header.core.bpp);
LOGMASKED(
LOG_DIB,
"Read DIB core header from image data : %d*%d, %u planes, %u bpp\n",
header.core.width,
header.core.height,
header.core.planes,
header.core.bpp);
// this works because the core header only aliases the width/height of the info header
header.info.bpp = header.core.bpp;
header.info.planes = header.core.planes;
header.info.height = header.core.height;
header.info.width = header.core.width;
header.info.size = 40U;
}
break;
default:
// the next version will be longer
if (124U >= header.core.size)
{
LOG(
"DIB image data (%u bytes) uses unsupported DIB header format (size %u)\n",
length,
header.core.size);
return msdib_error::UNSUPPORTED_FORMAT;
}
// fall through
case 40U:
case 52U:
case 56U:
case 108U:
case 124U:
// the Windows 3 bitmap header with optional extensions
{
palette_bytes = 4U;
std::uint32_t const header_read(std::min<std::uint32_t>(header.info.size, sizeof(header.info)) - sizeof(header.info.size));
if (fp.read(&header.info.width, header_read) != header_read)
{
LOG("Error reading DIB info header from image data (%u bytes)\n", length);
return msdib_error::FILE_TRUNCATED;
}
fp.seek(header.info.size - sizeof(header.info.size) - header_read, SEEK_CUR);
header.info.width = little_endianize_int32(header.info.width);
header.info.height = little_endianize_int32(header.info.height);
header.info.planes = little_endianize_int16(header.info.planes);
header.info.bpp = little_endianize_int16(header.info.bpp);
header.info.comp = little_endianize_int32(header.info.comp);
header.info.rawsize = little_endianize_int32(header.info.rawsize);
header.info.hres = little_endianize_int32(header.info.hres);
header.info.vres = little_endianize_int32(header.info.vres);
header.info.colors = little_endianize_int32(header.info.colors);
header.info.important = little_endianize_int32(header.info.important);
header.info.red = little_endianize_int32(header.info.red);
header.info.green = little_endianize_int32(header.info.green);
header.info.blue = little_endianize_int32(header.info.blue);
header.info.alpha = little_endianize_int32(header.info.alpha);
LOGMASKED(
LOG_DIB,
"Read DIB info header from image data: %d*%d (%d*%d ppm), %u planes, %u bpp %u/%s%u colors\n",
header.info.width,
header.info.height,
header.info.hres,
header.info.vres,
header.info.planes,
header.info.bpp,
header.info.important,
header.info.colors ? "" : "2^",
header.info.colors ? header.info.colors : header.info.bpp);
}
break;
}
// check for unsupported planes/bit depth
if ((1U != header.info.planes) || !header.info.bpp || (32U < header.info.bpp) || ((8U < header.info.bpp) ? (header.info.bpp % 8) : (8 % header.info.bpp)))
{
LOG("DIB image data uses unsupported planes/bits per pixel %u*%u\n", header.info.planes, header.info.bpp);
return msdib_error::UNSUPPORTED_FORMAT;
}
// check dimensions
if ((0 >= header.info.width) || (0 == header.info.height))
{
LOG("DIB image data has invalid dimensions %u*%u\n", header.info.width, header.info.height);
return msdib_error::FILE_CORRUPT;
}
// ensure compression scheme is supported
bool no_palette(false);
indexed = true;
switch (header.info.comp)
{
case DIB_COMP_NONE:
// uncompressed - direct colour with implied bitfields if more than eight bits/pixel
indexed = 8U >= header.info.bpp;
if (indexed)
{
if ((1U << header.info.bpp) < header.info.colors)
{
osd_printf_verbose(
"DIB image data has oversized palette with %u entries for %u bits per pixel\n",
header.info.colors,
header.info.bpp);
}
}
if (!indexed)
{
no_palette = true;
switch(header.info.bpp)
{
case 16U:
header.info.red = 0x00007c00;
header.info.green = 0x000003e0;
header.info.blue = 0x0000001f;
header.info.alpha = 0x00000000;
break;
case 24U:
case 32U:
header.info.red = 0x00ff0000;
header.info.green = 0x0000ff00;
header.info.blue = 0x000000ff;
header.info.alpha = 0x00000000;
break;
}
}
break;
case DIB_COMP_BITFIELDS:
// uncompressed direct colour with explicitly-specified bitfields
indexed = false;
if (offsetof(bitmap_info_header, alpha) > header.info.size)
{
osd_printf_verbose(
"DIB image data specifies bit masks but is too small (size %u)\n",
header.info.size);
return msdib_error::FILE_CORRUPT;
}
break;
default:
LOG("DIB image data uses unsupported compression scheme %u\n", header.info.comp);
return msdib_error::UNSUPPORTED_FORMAT;
}
// we can now calculate the size of the palette and row data
palette_entries =
indexed
? ((1U == header.info.bpp) ? 2U : header.info.colors ? header.info.colors : (1U << header.info.bpp))
: (no_palette ? 0U : header.info.colors);
palette_size = palette_bytes * palette_entries;
row_bytes = ((31 + (header.info.width * header.info.bpp)) >> 5) << 2;
// header looks OK
return msdib_error::NONE;
}
} // anonymous namespace
msdib_error msdib_verify_header(core_file &fp)
{
msdib_error err;
// file header
std::uint32_t file_length;
err = dib_read_file_header(fp, file_length);
if (msdib_error::NONE != err)
return err;
// bitmap header
bitmap_headers header;
unsigned palette_bytes;
bool indexed;
std::size_t palette_entries, palette_size, row_bytes;
err = dib_read_bitmap_header(
fp,
header,
palette_bytes,
indexed,
palette_entries,
palette_size,
row_bytes,
file_length - sizeof(bitmap_file_header));
if (msdib_error::NONE != err)
return err;
// check length
std::size_t const required_size(
sizeof(bitmap_file_header) +
header.info.size +
palette_size +
(row_bytes * std::abs(header.info.height)));
if (required_size > file_length)
return msdib_error::FILE_TRUNCATED;
// good chance this file is supported
return msdib_error::NONE;
}
msdib_error msdib_read_bitmap(core_file &fp, bitmap_argb32 &bitmap)
{
std::uint32_t file_length;
msdib_error const headerr(dib_read_file_header(fp, file_length));
if (msdib_error::NONE != headerr)
return headerr;
return msdib_read_bitmap_data(fp, bitmap, file_length - sizeof(bitmap_file_header), 0U);
}
msdib_error msdib_read_bitmap_data(core_file &fp, bitmap_argb32 &bitmap, std::uint32_t length, std::uint32_t dirheight)
{
// read the bitmap header
bitmap_headers header;
unsigned palette_bytes;
bool indexed;
std::size_t palette_entries, palette_size, row_bytes;
msdib_error const head_error(dib_read_bitmap_header(fp, header, palette_bytes, indexed, palette_entries, palette_size, row_bytes, length));
if (msdib_error::NONE != head_error)
return head_error;
// check dimensions
bool const top_down(0 > header.info.height);
if (top_down)
header.info.height = -header.info.height;
bool have_and_mask((2 * dirheight) == header.info.height);
if (!have_and_mask && (0 < dirheight) && (dirheight != header.info.height))
{
osd_printf_verbose(
"DIB image data height %d doesn't match directory height %u with or without AND mask\n",
header.info.height,
dirheight);
return msdib_error::UNSUPPORTED_FORMAT;
}
if (have_and_mask)
header.info.height >>= 1;
// we can now calculate the size of the image data
std::size_t const mask_row_bytes(((31 + header.info.width) >> 5) << 2);
std::size_t const required_size(
header.info.size +
palette_size +
((row_bytes + (have_and_mask ? mask_row_bytes : 0U)) * header.info.height));
if (required_size > length)
{
LOG("DIB image data (%u bytes) smaller than calculated DIB data size (%u bytes)\n", length, required_size);
return msdib_error::FILE_TRUNCATED;
}
// load the palette for indexed colour formats or the shifts for direct colour formats
unsigned red_shift(0), green_shift(0), blue_shift(0), alpha_shift(0);
unsigned red_bits(0), green_bits(0), blue_bits(0), alpha_bits(0);
std::unique_ptr<rgb_t []> palette;
if (indexed)
{
// read palette and convert
std::unique_ptr<std::uint8_t []> palette_data;
try { palette_data.reset(new std::uint8_t [palette_size]); }
catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
if (fp.read(palette_data.get(), palette_size) != palette_size)
{
LOG("Error reading palette from DIB image data (%u bytes)\n", length);
return msdib_error::FILE_TRUNCATED;
}
std::size_t const palette_usable(std::min<std::size_t>(palette_entries, std::size_t(1) << header.info.bpp));
try { palette.reset(new rgb_t [palette_usable]); }
catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
std::uint8_t const *ptr(palette_data.get());
for (std::size_t i = 0; palette_usable > i; ++i, ptr += palette_bytes)
palette[i] = rgb_t(ptr[2], ptr[1], ptr[0]);
}
else
{
// skip over the palette if necessary
if (palette_entries)
fp.seek(palette_bytes * palette_entries, SEEK_CUR);
// convert masks to shifts
bool const masks_contiguous(
dib_parse_mask(header.info.red, red_shift, red_bits) &&
dib_parse_mask(header.info.green, green_shift, green_bits) &&
dib_parse_mask(header.info.blue, blue_shift, blue_bits) &&
dib_parse_mask(header.info.alpha, alpha_shift, alpha_bits));
if (!masks_contiguous)
{
osd_printf_verbose(
"DIB image data specifies non-contiguous channel masks 0x%x | 0x%x | 0x%x | 0x%x\n",
header.info.red,
header.info.green,
header.info.blue,
header.info.alpha);
}
if ((32U != header.info.bpp) && ((header.info.red | header.info.green | header.info.blue | header.info.alpha) >> header.info.bpp))
{
LOG(
"DIB image data specifies channel masks 0x%x | 0x%x | 0x%x | 0x%x that exceed %u bits per pixel\n",
header.info.red,
header.info.green,
header.info.blue,
header.info.alpha,
header.info.bpp);
return msdib_error::FILE_CORRUPT;
}
LOGMASKED(
LOG_DIB,
"DIB image data using channels: R((x >> %2$u) & 0x%3$0*1$x) G((x >> %4$u) & 0x%5$0*1$x) B((x >> %6$u) & 0x%7$0*1$x) A((x >> %8$u) & 0x%9$0*1$x)\n",
(header.info.bpp + 3) >> 2,
red_shift,
(std::uint32_t(1) << red_bits) - 1,
green_shift,
(std::uint32_t(1) << green_bits) - 1,
blue_shift,
(std::uint32_t(1) << blue_bits) - 1,
alpha_shift,
(std::uint32_t(1) << alpha_bits) - 1);
// the MAME bitmap only supports 8 bits/sample maximum
dib_truncate_channel(red_shift, red_bits);
dib_truncate_channel(green_shift, green_bits);
dib_truncate_channel(blue_shift, blue_bits);
dib_truncate_channel(alpha_shift, alpha_bits);
}
// allocate the bitmap and process row data
std::unique_ptr<std::uint8_t []> row_data;
try {row_data.reset(new std::uint8_t [row_bytes]); }
catch (std::bad_alloc const &) { return msdib_error::OUT_OF_MEMORY; }
bitmap.allocate(header.info.width, header.info.height);
int const y_inc(top_down ? 1 : -1);
for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
{
if (fp.read(row_data.get(), row_bytes) != row_bytes)
{
LOG("Error reading DIB row %d data from image data\n", i);
return msdib_error::FILE_TRUNCATED;
}
std::uint8_t *src(row_data.get());
std::uint32_t *dest(&bitmap.pix(y));
unsigned shift(0U);
for (std::int32_t x = 0; header.info.width > x; ++x, ++dest)
{
// extract or compose a pixel
std::uint32_t pix(0U);
if (8U >= header.info.bpp)
{
assert(8U > shift);
pix = *src >> (8U - header.info.bpp);
*src <<= header.info.bpp;
shift += header.info.bpp;
if (8U <= shift)
{
shift = 0U;
++src;
}
}
else for (shift = 0; header.info.bpp > shift; shift += 8U, ++src)
{
pix |= std::uint32_t(*src) << shift;
}
// convert to RGB
if (indexed)
{
if (palette_entries > pix)
{
*dest = palette[pix];
}
else
{
*dest = rgb_t::transparent();
osd_printf_verbose(
"DIB image data has out-of-range color %u at (%d, %d) with %u palette entries\n",
pix,
x,
y,
palette_entries);
}
}
else
{
std::uint8_t r(dib_splat_sample((pix >> red_shift) & ((std::uint32_t(1) << red_bits) - 1), red_bits));
std::uint8_t g(dib_splat_sample((pix >> green_shift) & ((std::uint32_t(1) << green_bits) - 1), green_bits));
std::uint8_t b(dib_splat_sample((pix >> blue_shift) & ((std::uint32_t(1) << blue_bits) - 1), blue_bits));
std::uint8_t a(dib_splat_sample((pix >> alpha_shift) & ((std::uint32_t(1) << alpha_bits) - 1), alpha_bits));
*dest = rgb_t(alpha_bits ? a : 255, r, g, b);
}
}
}
// process the AND mask if present
if (have_and_mask)
{
for (std::int32_t i = 0, y = top_down ? 0 : (header.info.height - 1); header.info.height > i; ++i, y += y_inc)
{
if (fp.read(row_data.get(), mask_row_bytes) != mask_row_bytes)
{
LOG("Error reading DIB mask row %d data from image data\n", i);
return msdib_error::FILE_TRUNCATED;
}
std::uint8_t *src(row_data.get());
std::uint32_t *dest(&bitmap.pix(y));
unsigned shift(0U);
for (std::int32_t x = 0; header.info.width > x; ++x, ++dest)
{
assert(8U > shift);
rgb_t pix(*dest);
*dest = pix.set_a(BIT(*src, 7U - shift) ? 0U : pix.a());
if (8U <= ++shift)
{
shift = 0U;
++src;
}
}
}
}
// we're done!
return msdib_error::NONE;
}
} // namespace util

46
src/lib/util/msdib.h Normal file
View File

@ -0,0 +1,46 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
msdib.h
Microsoft Device-Independent Bitmap file loading.
***************************************************************************/
#ifndef MAME_LIB_UTIL_MSDIB_H
#define MAME_LIB_UTIL_MSDIB_H
#pragma once
#include "bitmap.h"
#include "corefile.h"
#include "osdcore.h"
#include <cstdint>
namespace util {
/***************************************************************************
CONSTANTS
***************************************************************************/
// Error types
enum class msdib_error
{
NONE,
OUT_OF_MEMORY,
FILE_ERROR,
BAD_SIGNATURE,
FILE_TRUNCATED,
FILE_CORRUPT,
UNSUPPORTED_FORMAT
};
msdib_error msdib_verify_header(core_file &fp);
msdib_error msdib_read_bitmap(core_file &fp, bitmap_argb32 &bitmap);
msdib_error msdib_read_bitmap_data(core_file &fp, bitmap_argb32 &bitmap, std::uint32_t length, std::uint32_t dirheight = 0U);
} // namespace util
#endif // MAME_LIB_UTIL_MSDIB_H

33
src/lib/util/nanosvg.h Normal file
View File

@ -0,0 +1,33 @@
// license:BSD-3-Clause
// copyright-holders:Vas Crabb
/***************************************************************************
nanosvg.h
NanoSVG helpers.
***************************************************************************/
#ifndef MAME_LIB_UTIL_NANOSVG_H
#define MAME_LIB_UTIL_NANOSVG_H
#include <nanosvg/src/nanosvg.h>
#include <nanosvg/src/nanosvgrast.h>
#include <memory>
namespace util {
struct nsvg_deleter
{
void operator()(NSVGimage *ptr) { nsvgDelete(ptr); }
void operator()(NSVGrasterizer *ptr) { nsvgDeleteRasterizer(ptr); }
};
using nsvg_image_ptr = std::unique_ptr<NSVGimage, nsvg_deleter>;
using nsvg_rasterizer_ptr = std::unique_ptr<NSVGrasterizer, nsvg_deleter>;
} // namespace util
#endif // MAME_LIB_UTIL_NANOSVG_H

View File

@ -2,7 +2,7 @@
// copyright-holders:Aaron Giles, Vas Crabb
/*********************************************************************
png.c
png.cpp
PNG reading functions.
@ -16,34 +16,13 @@
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
#include <cmath>
#include <new>
#include <cmath>
#include <cstdlib>
#include <cstring>
#include <new>
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
static const int samples[] = { 1, 0, 3, 1, 2, 0, 4 };
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
static inline int compute_rowbytes(const png_info &pnginfo)
{
return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8;
}
namespace util {
/***************************************************************************
GENERAL FUNCTIONS
@ -66,6 +45,12 @@ void png_info::free_data()
namespace {
/***************************************************************************
GLOBAL VARIABLES
***************************************************************************/
constexpr int samples[] = { 1, 0, 3, 1, 2, 0, 4 };
constexpr std::uint8_t PNG_SIGNATURE[] = { 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a };
#define MNG_Signature "\x8A\x4D\x4E\x47\x0D\x0A\x1A\x0A"
@ -101,6 +86,12 @@ constexpr std::uint8_t PNG_PF_Average = 3;
constexpr std::uint8_t PNG_PF_Paeth = 4;
/***************************************************************************
INLINE FUNCTIONS
***************************************************************************/
inline int compute_rowbytes(const png_info &pnginfo) { return (pnginfo.width * samples[pnginfo.color_type] * pnginfo.bit_depth + 7) / 8; }
inline uint8_t fetch_8bit(uint8_t const *v) { return *v; }
inline uint16_t fetch_16bit(uint8_t const *v) { return big_endianize_int16(*reinterpret_cast<uint16_t const *>(v)); }
inline uint32_t fetch_32bit(uint8_t const *v) { return big_endianize_int32(*reinterpret_cast<uint32_t const *>(v)); }
@ -132,11 +123,11 @@ private:
{
// do some basic checks for unsupported images
if (!pnginfo.bit_depth || (ARRAY_LENGTH(samples) <= pnginfo.color_type) || !samples[pnginfo.color_type])
return PNGERR_UNSUPPORTED_FORMAT; // unknown colour format
return png_error::UNSUPPORTED_FORMAT; // unknown colour format
if ((0 != pnginfo.interlace_method) && (1 != pnginfo.interlace_method))
return PNGERR_UNSUPPORTED_FORMAT; // unknown interlace method
return png_error::UNSUPPORTED_FORMAT; // unknown interlace method
if ((3 == pnginfo.color_type) && (!pnginfo.num_palette || !pnginfo.palette))
return PNGERR_FILE_CORRUPT; // indexed colour with no palette
return png_error::FILE_CORRUPT; // indexed colour with no palette
// calculate the offset for each pass of the interlace
unsigned const pass_count(get_pass_count());
@ -146,13 +137,13 @@ private:
// allocate memory for the filtered image
try { pnginfo.image.reset(new std::uint8_t [pass_offset[pass_count]]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
// decompress image data
png_error error = PNGERR_NONE;
png_error error = png_error::NONE;
error = decompress(idata, pass_offset[pass_count]);
std::uint32_t const bpp(get_bytes_per_pixel());
for (unsigned pass = 0; (pass_count > pass) && (PNGERR_NONE == error); ++pass)
for (unsigned pass = 0; (pass_count > pass) && (png_error::NONE == error); ++pass)
{
// compute some basic parameters
std::pair<std::uint32_t, std::uint32_t> const dimensions(get_pass_dimensions(pass));
@ -161,7 +152,7 @@ private:
// we de-filter in place, stripping the filter bytes off the rows
uint8_t *dst(&pnginfo.image[pass_offset[pass]]);
uint8_t const *src(dst);
for (std::uint32_t y = 0; (dimensions.second > y) && (PNGERR_NONE == error); ++y)
for (std::uint32_t y = 0; (dimensions.second > y) && (png_error::NONE == error); ++y)
{
// first byte of each row is the filter type
uint8_t const filter(*src++);
@ -172,7 +163,7 @@ private:
}
// if we errored, free the image data
if (error != PNGERR_NONE)
if (error != png_error::NONE)
pnginfo.image.reset();
return error;
@ -182,7 +173,7 @@ private:
{
// only deflate is permitted
if (0 != pnginfo.compression_method)
return PNGERR_DECOMPRESS_ERROR;
return png_error::DECOMPRESS_ERROR;
// allocate zlib stream
z_stream stream;
@ -195,7 +186,7 @@ private:
stream.next_in = Z_NULL;
zerr = inflateInit(&stream);
if (Z_OK != zerr)
return PNGERR_DECOMPRESS_ERROR;
return png_error::DECOMPRESS_ERROR;
// decompress IDAT blocks
stream.next_out = pnginfo.image.get();
@ -217,28 +208,28 @@ private:
// it's all good if we got end-of-stream or we have with no data remaining
if ((Z_OK == inflateEnd(&stream)) && ((Z_STREAM_END == zerr) || ((Z_OK == zerr) && (idata.end() == it) && !stream.avail_in)))
return PNGERR_NONE;
return png_error::NONE;
else
return PNGERR_DECOMPRESS_ERROR;
return png_error::DECOMPRESS_ERROR;
}
png_error unfilter_row(std::uint8_t type, uint8_t const *src, uint8_t *dst, uint8_t const *dstprev, int bpp, std::uint32_t rowbytes)
{
if (0 != pnginfo.filter_method)
return PNGERR_UNKNOWN_FILTER;
return png_error::UNKNOWN_FILTER;
switch (type)
{
case PNG_PF_None: // no filter, just copy
std::copy_n(src, rowbytes, dst);
return PNGERR_NONE;
return png_error::NONE;
case PNG_PF_Sub: // SUB = previous pixel
dst = std::copy_n(src, bpp, dst);
src += bpp;
for (std::uint32_t x = bpp; rowbytes > x; ++x, ++src, ++dst)
*dst = *src + dst[-bpp];
return PNGERR_NONE;
return png_error::NONE;
case PNG_PF_Up: // UP = pixel above
if (dstprev)
@ -250,7 +241,7 @@ private:
{
std::copy_n(src, rowbytes, dst);
}
return PNGERR_NONE;
return png_error::NONE;
case PNG_PF_Average: // AVERAGE = average of pixel above and previous pixel
if (dstprev)
@ -267,7 +258,7 @@ private:
for (std::uint32_t x = bpp; rowbytes > x; ++x, ++src, ++dst)
*dst = *src + (dst[-bpp] >> 1);
}
return PNGERR_NONE;
return png_error::NONE;
case PNG_PF_Paeth: // PAETH = special filter
for (std::uint32_t x = 0; rowbytes > x; ++x, ++src, ++dst)
@ -281,10 +272,10 @@ private:
int32_t const dc(std::abs(prediction - pc));
*dst = ((da <= db) && (da <= dc)) ? (*src + pa) : (db <= dc) ? (*src + pb) : (*src + pc);
}
return PNGERR_NONE;
return png_error::NONE;
default: // unknown filter type
return PNGERR_UNKNOWN_FILTER;
return png_error::UNKNOWN_FILTER;
}
}
@ -294,7 +285,7 @@ private:
{
case PNG_CN_IHDR: // image header
if (13 > length)
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
pnginfo.width = fetch_32bit(&data[0]);
pnginfo.height = fetch_32bit(&data[4]);
pnginfo.bit_depth = fetch_8bit(&data[8]);
@ -307,31 +298,31 @@ private:
case PNG_CN_PLTE: // palette
pnginfo.num_palette = length / 3;
if ((length % 3) || ((3 == pnginfo.color_type) && ((1 << pnginfo.bit_depth) < pnginfo.num_palette)))
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
pnginfo.palette = std::move(data);
break;
case PNG_CN_tRNS: // transparency information
if (((0 == pnginfo.color_type) && (2 > length)) || ((2 == pnginfo.color_type) && (6 > length)))
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
pnginfo.num_trans = length;
pnginfo.trans = std::move(data);
break;
case PNG_CN_IDAT: // image data
try { idata.emplace_back(length, std::move(data)); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
break;
case PNG_CN_gAMA: // gamma
if (4 > length)
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
pnginfo.source_gamma = fetch_32bit(data.get()) / 100000.0;
break;
case PNG_CN_pHYs: // physical information
if (9 > length)
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
pnginfo.xres = fetch_32bit(&data[0]);
pnginfo.yres = fetch_32bit(&data[4]);
pnginfo.resolution_unit = fetch_8bit(&data[8]);
@ -361,17 +352,17 @@ private:
}
catch (std::bad_alloc const &)
{
return PNGERR_OUT_OF_MEMORY;
return png_error::OUT_OF_MEMORY;
}
break;
/* anything else */
default:
if ((type & 0x20000000) == 0)
return PNGERR_UNKNOWN_CHUNK;
return png_error::UNKNOWN_CHUNK;
break;
}
return PNGERR_NONE;
return png_error::NONE;
}
unsigned get_pass_count() const
@ -413,23 +404,23 @@ private:
return ((samples[pnginfo.color_type] * pnginfo.bit_depth) + 7) >> 3;
}
static png_error read_chunk(util::core_file &fp, std::unique_ptr<std::uint8_t []> &data, std::uint32_t &type, std::uint32_t &length)
static png_error read_chunk(core_file &fp, std::unique_ptr<std::uint8_t []> &data, std::uint32_t &type, std::uint32_t &length)
{
std::uint8_t tempbuff[4];
/* fetch the length of this chunk */
if (fp.read(tempbuff, 4) != 4)
return PNGERR_FILE_TRUNCATED;
return png_error::FILE_TRUNCATED;
length = fetch_32bit(tempbuff);
/* fetch the type of this chunk */
if (fp.read(tempbuff, 4) != 4)
return PNGERR_FILE_TRUNCATED;
return png_error::FILE_TRUNCATED;
type = fetch_32bit(tempbuff);
/* stop when we hit an IEND chunk */
if (type == PNG_CN_IEND)
return PNGERR_NONE;
return png_error::NONE;
/* start the CRC with the chunk type (but not the length) */
std::uint32_t crc = crc32(0, tempbuff, 4);
@ -439,13 +430,13 @@ private:
{
/* allocate memory for this chunk */
try { data.reset(new std::uint8_t [length]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
/* read the data from the file */
if (fp.read(data.get(), length) != length)
{
data.reset();
return PNGERR_FILE_TRUNCATED;
return png_error::FILE_TRUNCATED;
}
/* update the CRC */
@ -456,7 +447,7 @@ private:
if (fp.read(tempbuff, 4) != 4)
{
data.reset();
return PNGERR_FILE_TRUNCATED;
return png_error::FILE_TRUNCATED;
}
std::uint32_t const chunk_crc = fetch_32bit(tempbuff);
@ -464,10 +455,10 @@ private:
if (crc != chunk_crc)
{
data.reset();
return PNGERR_FILE_CORRUPT;
return png_error::FILE_CORRUPT;
}
return PNGERR_NONE;
return png_error::NONE;
}
png_info & pnginfo;
@ -481,13 +472,13 @@ public:
{
// do some basic checks for unsupported images
if ((8 > pnginfo.bit_depth) || (pnginfo.bit_depth % 8))
return PNGERR_UNSUPPORTED_FORMAT; // only do multiples of 8bps here - expand lower bit depth first
return png_error::UNSUPPORTED_FORMAT; // only do multiples of 8bps here - expand lower bit depth first
if ((ARRAY_LENGTH(samples) <= pnginfo.color_type) || !samples[pnginfo.color_type])
return PNGERR_UNSUPPORTED_FORMAT; // unknown colour sample format
return png_error::UNSUPPORTED_FORMAT; // unknown colour sample format
if ((0 != pnginfo.interlace_method) && (1 != pnginfo.interlace_method))
return PNGERR_UNSUPPORTED_FORMAT; // unknown interlace method
return png_error::UNSUPPORTED_FORMAT; // unknown interlace method
if ((3 == pnginfo.color_type) && (8 != pnginfo.bit_depth))
return PNGERR_UNSUPPORTED_FORMAT; // indexed colour must be exactly 8bpp
return png_error::UNSUPPORTED_FORMAT; // indexed colour must be exactly 8bpp
// everything looks sane, allocate the bitmap and deinterlace into it
bitmap.allocate(pnginfo.width, pnginfo.height);
@ -603,22 +594,22 @@ public:
// set hasalpha flag and return
hasalpha = 0xffU != accumalpha;
return PNGERR_NONE;
return png_error::NONE;
}
png_error expand_buffer_8bit()
{
// nothing to do if we're at 8 or greater already
if (pnginfo.bit_depth >= 8)
return PNGERR_NONE;
return png_error::NONE;
// do some basic checks for unsupported images
if (!pnginfo.bit_depth || (8 % pnginfo.bit_depth))
return PNGERR_UNSUPPORTED_FORMAT; // bit depth must be a factor of eight
return png_error::UNSUPPORTED_FORMAT; // bit depth must be a factor of eight
if ((0 != pnginfo.color_type) && (3 != pnginfo.color_type))
return PNGERR_UNSUPPORTED_FORMAT; // only upsample monochrome and indexed colour
return png_error::UNSUPPORTED_FORMAT; // only upsample monochrome and indexed colour
if ((0 != pnginfo.interlace_method) && (1 != pnginfo.interlace_method))
return PNGERR_UNSUPPORTED_FORMAT; // unknown interlace method
return png_error::UNSUPPORTED_FORMAT; // unknown interlace method
// calculate the offset for each pass of the interlace on the input and output
unsigned const pass_count(get_pass_count());
@ -633,7 +624,7 @@ public:
// allocate a new buffer at 8-bit
std::unique_ptr<std::uint8_t []> outbuf;
try { outbuf.reset(new std::uint8_t [outp_offset[pass_count]]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
// upsample bitmap
std::uint8_t const bytesamples(8 / pnginfo.bit_depth);
@ -687,13 +678,13 @@ public:
pnginfo.image = std::move(outbuf);
pnginfo.bit_depth = 8;
return PNGERR_NONE;
return png_error::NONE;
}
png_error read_file(util::core_file &fp)
png_error read_file(core_file &fp)
{
// initialize the data structures
png_error error = PNGERR_NONE;
png_error error = png_error::NONE;
pnginfo.reset();
std::list<image_data_chunk> idata;
@ -701,13 +692,13 @@ public:
error = verify_header(fp);
// loop until we hit an IEND chunk
while (PNGERR_NONE == error)
while (png_error::NONE == error)
{
// read a chunk
std::unique_ptr<std::uint8_t []> chunk_data;
std::uint32_t chunk_type = 0, chunk_length;
error = read_chunk(fp, chunk_data, chunk_type, chunk_length);
if (PNGERR_NONE == error)
if (png_error::NONE == error)
{
if (chunk_type == PNG_CN_IEND)
break; // stop when we hit an IEND chunk
@ -717,29 +708,29 @@ public:
}
// finish processing the image
if (PNGERR_NONE == error)
if (png_error::NONE == error)
error = process(idata);
// if we have an error, free all the output data
if (error != PNGERR_NONE)
if (error != png_error::NONE)
pnginfo.reset();
return error;
}
static png_error verify_header(util::core_file &fp)
static png_error verify_header(core_file &fp)
{
EQUIVALENT_ARRAY(PNG_SIGNATURE, std::uint8_t) signature;
// read 8 bytes
if (fp.read(signature, sizeof(signature)) != sizeof(signature))
return PNGERR_FILE_TRUNCATED;
return png_error::FILE_TRUNCATED;
// return an error if we don't match
if (std::memcmp(signature, PNG_SIGNATURE, sizeof(PNG_SIGNATURE)))
return PNGERR_BAD_SIGNATURE;
return png_error::BAD_SIGNATURE;
return PNGERR_NONE;
return png_error::NONE;
}
};
@ -760,7 +751,7 @@ constexpr unsigned png_private::ADAM7_Y_OFFS[7];
core stream
-------------------------------------------------*/
png_error png_info::verify_header(util::core_file &fp)
png_error png_info::verify_header(core_file &fp)
{
return png_private::verify_header(fp);
}
@ -770,7 +761,7 @@ png_error png_info::verify_header(util::core_file &fp)
read_file - read a PNG from a core stream
-------------------------------------------------*/
png_error png_info::read_file(util::core_file &fp)
png_error png_info::read_file(core_file &fp)
{
return png_private(*this).read_file(fp);
}
@ -781,7 +772,7 @@ png_error png_info::read_file(util::core_file &fp)
bitmap
-------------------------------------------------*/
png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap)
png_error png_read_bitmap(core_file &fp, bitmap_argb32 &bitmap)
{
png_error result;
png_info pnginfo;
@ -789,12 +780,12 @@ png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap)
// read the PNG data
result = png.read_file(fp);
if (PNGERR_NONE != result)
if (png_error::NONE != result)
return result;
// resample to 8bpp if necessary
result = png.expand_buffer_8bit();
if (PNGERR_NONE != result)
if (png_error::NONE != result)
{
pnginfo.free_data();
return result;
@ -848,13 +839,13 @@ png_error png_info::add_text(const char *keyword, const char *text)
char32_t ch;
int const len(uchar_from_utf8(&ch, ptr, kwend - ptr));
if ((0 >= len) || (32 > ch) || (255 < ch) || ((126 < ch) && (161 > ch)) || (((32 == prev) || (keyword == ptr)) && (32 == ch)))
return PNGERR_UNSUPPORTED_FORMAT;
return png_error::UNSUPPORTED_FORMAT;
prev = ch;
++cnt;
ptr += len;
}
if ((32 == prev) || (1 > cnt) || (79 < cnt))
return PNGERR_UNSUPPORTED_FORMAT;
return png_error::UNSUPPORTED_FORMAT;
// apply rules to text
char const *const textend(text + std::strlen(text));
@ -863,14 +854,14 @@ png_error png_info::add_text(const char *keyword, const char *text)
char32_t ch;
int const len(uchar_from_utf8(&ch, ptr, textend - ptr));
if ((0 >= len) || (1 > ch) || (255 < ch))
return PNGERR_UNSUPPORTED_FORMAT;
return png_error::UNSUPPORTED_FORMAT;
ptr += len;
}
// allocate a new text element
try { textlist.emplace_back(std::piecewise_construct, std::forward_as_tuple(keyword, kwend), std::forward_as_tuple(text, textend)); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
return PNGERR_NONE;
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
return png_error::NONE;
}
@ -879,7 +870,7 @@ png_error png_info::add_text(const char *keyword, const char *text)
the given file
-------------------------------------------------*/
static png_error write_chunk(util::core_file &fp, const uint8_t *data, uint32_t type, uint32_t length)
static png_error write_chunk(core_file &fp, const uint8_t *data, uint32_t type, uint32_t length)
{
uint8_t tempbuff[8];
uint32_t crc;
@ -891,22 +882,22 @@ static png_error write_chunk(util::core_file &fp, const uint8_t *data, uint32_t
/* write that data */
if (fp.write(tempbuff, 8) != 8)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
/* append the actual data */
if (length > 0)
{
if (fp.write(data, length) != length)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
crc = crc32(crc, data, length);
}
/* write the CRC */
put_32bit(tempbuff, crc);
if (fp.write(tempbuff, 4) != 4)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
return PNGERR_NONE;
return png_error::NONE;
}
@ -915,7 +906,7 @@ static png_error write_chunk(util::core_file &fp, const uint8_t *data, uint32_t
chunk to the given file by deflating it
-------------------------------------------------*/
static png_error write_deflated_chunk(util::core_file &fp, uint8_t *data, uint32_t type, uint32_t length)
static png_error write_deflated_chunk(core_file &fp, uint8_t *data, uint32_t type, uint32_t length)
{
uint64_t lengthpos = fp.tell();
uint8_t tempbuff[8192];
@ -931,7 +922,7 @@ static png_error write_deflated_chunk(util::core_file &fp, uint8_t *data, uint32
/* write that data */
if (fp.write(tempbuff, 8) != 8)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
/* initialize the stream */
memset(&stream, 0, sizeof(stream));
@ -939,7 +930,7 @@ static png_error write_deflated_chunk(util::core_file &fp, uint8_t *data, uint32
stream.avail_in = length;
zerr = deflateInit(&stream, Z_DEFAULT_COMPRESSION);
if (zerr != Z_OK)
return PNGERR_COMPRESS_ERROR;
return png_error::COMPRESS_ERROR;
/* now loop until we run out of data */
for ( ; ; )
@ -956,7 +947,7 @@ static png_error write_deflated_chunk(util::core_file &fp, uint8_t *data, uint32
if (fp.write(tempbuff, bytes) != bytes)
{
deflateEnd(&stream);
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
}
crc = crc32(crc, tempbuff, bytes);
zlength += bytes;
@ -970,29 +961,29 @@ static png_error write_deflated_chunk(util::core_file &fp, uint8_t *data, uint32
if (zerr != Z_OK)
{
deflateEnd(&stream);
return PNGERR_COMPRESS_ERROR;
return png_error::COMPRESS_ERROR;
}
}
/* clean up deflater(maus) */
zerr = deflateEnd(&stream);
if (zerr != Z_OK)
return PNGERR_COMPRESS_ERROR;
return png_error::COMPRESS_ERROR;
/* write the CRC */
put_32bit(tempbuff, crc);
if (fp.write(tempbuff, 4) != 4)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
/* seek back and update the length */
fp.seek(lengthpos, SEEK_SET);
put_32bit(tempbuff + 0, zlength);
if (fp.write(tempbuff, 4) != 4)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
/* return to the end */
fp.seek(lengthpos + 8 + zlength + 4, SEEK_SET);
return PNGERR_NONE;
return png_error::NONE;
}
@ -1016,7 +1007,7 @@ static png_error convert_bitmap_to_image_palette(png_info &pnginfo, const bitmap
/* allocate memory for the palette */
try { pnginfo.palette.reset(new std::uint8_t [3 * 256]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
/* build the palette */
std::fill_n(pnginfo.palette.get(), 3 * 256, 0);
@ -1036,7 +1027,7 @@ static png_error convert_bitmap_to_image_palette(png_info &pnginfo, const bitmap
catch (std::bad_alloc const &)
{
pnginfo.palette.reset();
return PNGERR_OUT_OF_MEMORY;
return png_error::OUT_OF_MEMORY;
}
/* copy in the pixels, specifying a nullptr filter */
@ -1051,7 +1042,7 @@ static png_error convert_bitmap_to_image_palette(png_info &pnginfo, const bitmap
*dst++ = *src++;
}
return PNGERR_NONE;
return png_error::NONE;
}
@ -1075,7 +1066,7 @@ static png_error convert_bitmap_to_image_rgb(png_info &pnginfo, const bitmap_t &
/* allocate memory for the image */
try { pnginfo.image.reset(new std::uint8_t [pnginfo.height * (rowbytes + 1)]); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
/* copy in the pixels, specifying a nullptr filter */
for (y = 0; y < pnginfo.height; y++)
@ -1127,10 +1118,10 @@ static png_error convert_bitmap_to_image_rgb(png_info &pnginfo, const bitmap_t &
/* unsupported format */
else
return PNGERR_UNSUPPORTED_FORMAT;
return png_error::UNSUPPORTED_FORMAT;
}
return PNGERR_NONE;
return png_error::NONE;
}
@ -1139,7 +1130,7 @@ static png_error convert_bitmap_to_image_rgb(png_info &pnginfo, const bitmap_t &
chunks to the given file
-------------------------------------------------*/
static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette)
static png_error write_png_stream(core_file &fp, png_info &pnginfo, const bitmap_t &bitmap, int palette_length, const rgb_t *palette)
{
uint8_t tempbuff[16];
png_error error;
@ -1149,7 +1140,7 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const
error = convert_bitmap_to_image_palette(pnginfo, bitmap, palette_length, palette);
else
error = convert_bitmap_to_image_rgb(pnginfo, bitmap, palette_length, palette);
if (error != PNGERR_NONE)
if (error != png_error::NONE)
return error;
// if we wanted to get clever and do filtering, we would do it here
@ -1163,18 +1154,18 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const
put_8bit(tempbuff + 11, pnginfo.filter_method);
put_8bit(tempbuff + 12, pnginfo.interlace_method);
error = write_chunk(fp, tempbuff, PNG_CN_IHDR, 13);
if (error != PNGERR_NONE)
if (error != png_error::NONE)
return error;
// write the PLTE chunk
if (pnginfo.num_palette > 0)
error = write_chunk(fp, pnginfo.palette.get(), PNG_CN_PLTE, pnginfo.num_palette * 3);
if (error != PNGERR_NONE)
if (error != png_error::NONE)
return error;
// write a single IDAT chunk */
// write a single IDAT chunk
error = write_deflated_chunk(fp, pnginfo.image.get(), PNG_CN_IDAT, pnginfo.height * (compute_rowbytes(pnginfo) + 1));
if (error != PNGERR_NONE)
if (error != png_error::NONE)
return error;
// write TEXT chunks
@ -1182,7 +1173,7 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const
for (png_info::png_text const &text : pnginfo.textlist)
{
try { textbuf.resize(text.first.length() + 1 + text.second.length()); }
catch (std::bad_alloc const &) { return PNGERR_OUT_OF_MEMORY; }
catch (std::bad_alloc const &) { return png_error::OUT_OF_MEMORY; }
std::uint8_t *dst(&textbuf[0]);
// convert keyword to ISO-8859-1
@ -1213,7 +1204,7 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const
}
error = write_chunk(fp, &textbuf[0], PNG_CN_tEXt, dst - &textbuf[0]);
if (error != PNGERR_NONE)
if (error != png_error::NONE)
return error;
}
@ -1222,7 +1213,7 @@ static png_error write_png_stream(util::core_file &fp, png_info &pnginfo, const
}
png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette)
png_error png_write_bitmap(core_file &fp, png_info *info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette)
{
// use a dummy pnginfo if none passed to us
png_info pnginfo;
@ -1231,7 +1222,7 @@ png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t const &
// write the PNG signature
if (fp.write(PNG_SIGNATURE, sizeof(PNG_SIGNATURE)) != sizeof(PNG_SIGNATURE))
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
/* write the rest of the PNG data */
return write_png_stream(fp, *info, bitmap, palette_length, palette);
@ -1246,7 +1237,7 @@ png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t const &
********************************************************************************/
/**
* @fn png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, unsigned rate)
* @fn png_error mng_capture_start(core_file &fp, bitmap_t &bitmap, unsigned rate)
*
* @brief Mng capture start.
*
@ -1257,12 +1248,12 @@ png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t const &
* @return A png_error.
*/
png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, unsigned rate)
png_error mng_capture_start(core_file &fp, bitmap_t &bitmap, unsigned rate)
{
uint8_t mhdr[28];
if (fp.write(MNG_Signature, 8) != 8)
return PNGERR_FILE_ERROR;
return png_error::FILE_ERROR;
memset(mhdr, 0, 28);
put_32bit(mhdr + 0, bitmap.width());
@ -1273,7 +1264,7 @@ png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, unsigned rate
}
/**
* @fn png_error mng_capture_frame(util::core_file &fp, png_info *info, bitmap_t &bitmap, int palette_length, const rgb_t *palette)
* @fn png_error mng_capture_frame(core_file &fp, png_info *info, bitmap_t &bitmap, int palette_length, const rgb_t *palette)
*
* @brief Mng capture frame.
*
@ -1286,13 +1277,13 @@ png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, unsigned rate
* @return A png_error.
*/
png_error mng_capture_frame(util::core_file &fp, png_info &info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette)
png_error mng_capture_frame(core_file &fp, png_info &info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette)
{
return write_png_stream(fp, info, bitmap, palette_length, palette);
}
/**
* @fn png_error mng_capture_stop(util::core_file &fp)
* @fn png_error mng_capture_stop(core_file &fp)
*
* @brief Mng capture stop.
*
@ -1301,7 +1292,9 @@ png_error mng_capture_frame(util::core_file &fp, png_info &info, bitmap_t const
* @return A png_error.
*/
png_error mng_capture_stop(util::core_file &fp)
png_error mng_capture_stop(core_file &fp)
{
return write_chunk(fp, nullptr, MNG_CN_MEND, 0);
}
} // namespace util

View File

@ -17,31 +17,33 @@
#include "corefile.h"
#include "osdcore.h"
#include <cstdint>
#include <list>
#include <memory>
#include <string>
#include <utility>
namespace util {
/***************************************************************************
CONSTANTS
***************************************************************************/
/* Error types */
enum png_error
enum class png_error
{
PNGERR_NONE,
PNGERR_OUT_OF_MEMORY,
PNGERR_UNKNOWN_FILTER,
PNGERR_FILE_ERROR,
PNGERR_BAD_SIGNATURE,
PNGERR_DECOMPRESS_ERROR,
PNGERR_FILE_TRUNCATED,
PNGERR_FILE_CORRUPT,
PNGERR_UNKNOWN_CHUNK,
PNGERR_COMPRESS_ERROR,
PNGERR_UNSUPPORTED_FORMAT
NONE,
OUT_OF_MEMORY,
UNKNOWN_FILTER,
FILE_ERROR,
BAD_SIGNATURE,
DECOMPRESS_ERROR,
FILE_TRUNCATED,
FILE_CORRUPT,
UNKNOWN_CHUNK,
COMPRESS_ERROR,
UNSUPPORTED_FORMAT
};
@ -57,7 +59,7 @@ public:
~png_info() { free_data(); }
png_error read_file(util::core_file &fp);
png_error read_file(core_file &fp);
png_error copy_to_bitmap(bitmap_argb32 &bitmap, bool &hasalpha);
png_error expand_buffer_8bit();
@ -66,7 +68,7 @@ public:
void free_data();
void reset() { free_data(); operator=(png_info()); }
static png_error verify_header(util::core_file &fp);
static png_error verify_header(core_file &fp);
std::unique_ptr<std::uint8_t []> image;
std::uint32_t width, height;
@ -99,12 +101,14 @@ private:
FUNCTION PROTOTYPES
***************************************************************************/
png_error png_read_bitmap(util::core_file &fp, bitmap_argb32 &bitmap);
png_error png_read_bitmap(core_file &fp, bitmap_argb32 &bitmap);
png_error png_write_bitmap(util::core_file &fp, png_info *info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette);
png_error png_write_bitmap(core_file &fp, png_info *info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette);
png_error mng_capture_start(util::core_file &fp, bitmap_t &bitmap, unsigned rate);
png_error mng_capture_frame(util::core_file &fp, png_info &info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette);
png_error mng_capture_stop(util::core_file &fp);
png_error mng_capture_start(core_file &fp, bitmap_t &bitmap, unsigned rate);
png_error mng_capture_frame(core_file &fp, png_info &info, bitmap_t const &bitmap, int palette_length, const rgb_t *palette);
png_error mng_capture_stop(core_file &fp);
} // namespace util
#endif // MAME_LIB_UTIL_PNG_H

View File

@ -30,7 +30,6 @@
#include "machine/ldp1450.h"
#include "machine/nvram.h"
#include "machine/amigafdc.h"
#include "render.h"
#include "speaker.h"

View File

@ -26,7 +26,6 @@ ToDo:
#include "by17.lh"
#include "by17_pwerplay.lh"
#include "by17_matahari.lh"
#include "render.h"
class by17_state : public genpin_class
{
@ -980,10 +979,6 @@ void by17_state::machine_reset()
{
genpin_class::machine_reset();
render_target *target = machine().render().first_target();
target->set_view(0);
m_u10a = 0;
m_u10b = 0;
m_u11a = 0;

View File

@ -73,7 +73,6 @@ ToDo:
#include "machine/6821pia.h"
#include "machine/timer.h"
#include "audio/bally.h"
#include "render.h"
#include "speaker.h"
//#define VERBOSE 1
@ -1441,10 +1440,6 @@ void by35_state::machine_reset()
{
genpin_class::machine_reset();
render_target *target = machine().render().first_target();
target->set_view(0);
m_u10a = 0;
m_u10b = 0;
m_u11a = 0;

View File

@ -83,7 +83,6 @@ Side 2 = 0x8F7DDD (or 0x880000 | ( 0x77 << 12 ) | 0x0DDD)
#include "video/tms9928a.h"
#include "machine/nvram.h"
#include "render.h"
#include "speaker.h"

View File

@ -19,7 +19,7 @@
#include "machine/i8251.h"
#include "machine/input_merger.h"
#include "sound/beep.h"
#include "render.h"
#include "screen.h"
#include "speaker.h"

View File

@ -46,7 +46,6 @@
#include "sound/ay8910.h"
#include "sound/spkrdev.h"
#include "emupal.h"
#include "render.h"
#include "speaker.h"
#include "dlair.lh"

View File

@ -26,7 +26,6 @@ Todo:
#include "machine/gen_latch.h"
#include "machine/ldv1000.h"
#include "emupal.h"
#include "render.h"
#include "speaker.h"

View File

@ -69,7 +69,6 @@ Dumping Notes:
#include "cpu/z80/z80.h"
#include "machine/ldv1000.h"
#include "emupal.h"
#include "render.h"
#include "speaker.h"

View File

@ -6,24 +6,6 @@ license:CC0
<!-- define elements -->
<element name="black"><rect><color red="0" green="0" blue="0" /></rect></element>
<element name="wandl">
<rect>
<bounds x="0" y="0" width="18" height="1" />
<color red="0" green="0" blue="0" />
</rect>
<disk>
<bounds state="0" x="0" y="0" width="1" height="1" />
<bounds state="11" x="16.5" y="0" width="1" height="1" />
<color red="1.0" green="0.2" blue="0.23" />
</disk>
</element>
<element name="wandm" defstate="0">
<rect state="0"><color red="0.1" green="0.1" blue="0.1" /></rect>
<rect state="1"><color red="1.0" green="1.0" blue="1.0" /></rect>
</element>
<element name="ledy" defstate="0">
<disk state="0"><color red="0.07" green="0.06" blue="0.02" /></disk>
<disk state="1"><color red="1.0" green="0.95" blue="0.2" /></disk>
@ -32,6 +14,11 @@ license:CC0
<disk state="0"><color red="0.023" green="0.1" blue="0.02" /></disk>
<disk state="1"><color red="0.23" green="1.0" blue="0.2" /></disk>
</element>
<element name="ledr" defstate="0">
<disk state="0"><color red="0.1" green="0.02" blue="0.023" /></disk>
<disk state="1"><color red="1.0" green="0.2" blue="0.23" /></disk>
</element>
<!-- build screen -->
@ -41,21 +28,24 @@ license:CC0
<!-- wand -->
<repeat count="9">
<repeat count="4">
<param name="y" start="1" increment="3" />
<element ref="black" inputtag="IN.1" inputmask="0x00" inputraw="yes"><bounds x="0" y="~y~" width="18" height="1" /></element>
<element ref="wandl" inputtag="IN.1" inputmask="0x0f" inputraw="yes"><bounds x="0" y="~y~" width="18" height="1" /></element>
<param name="n" start="0" increment="1" />
<element ref="ledr" name="~n~.7">
<animate inputtag="IN.1" mask="0x0f" />
<bounds state="0" x="0" y="~y~" width="1" height="1" />
<bounds state="11" x="16.5" y="~y~" width="1" height="1" />
</element>
</repeat>
<repeat count="5">
<param name="y" start="13" increment="3" />
<param name="n" start="0" increment="1" />
<element ref="ledr" name="~n~.6">
<animate inputtag="IN.1" mask="0x0f" />
<bounds state="0" x="0" y="~y~" width="1" height="1" />
<bounds state="11" x="16.5" y="~y~" width="1" height="1" />
</element>
</repeat>
<element name="0.7" ref="wandm" blend="multiply"><bounds x="-0.5" y="0.5" width="19" height="2" /></element>
<element name="1.7" ref="wandm" blend="multiply"><bounds x="-0.5" y="3.5" width="19" height="2" /></element>
<element name="2.7" ref="wandm" blend="multiply"><bounds x="-0.5" y="6.5" width="19" height="2" /></element>
<element name="3.7" ref="wandm" blend="multiply"><bounds x="-0.5" y="9.5" width="19" height="2" /></element>
<element name="0.6" ref="wandm" blend="multiply"><bounds x="-0.5" y="12.5" width="19" height="2" /></element>
<element name="1.6" ref="wandm" blend="multiply"><bounds x="-0.5" y="15.5" width="19" height="2" /></element>
<element name="2.6" ref="wandm" blend="multiply"><bounds x="-0.5" y="18.5" width="19" height="2" /></element>
<element name="3.6" ref="wandm" blend="multiply"><bounds x="-0.5" y="21.5" width="19" height="2" /></element>
<element name="4.6" ref="wandm" blend="multiply"><bounds x="-0.5" y="24.5" width="19" height="2" /></element>
<!-- invaders -->

View File

@ -1018,7 +1018,7 @@ void midtunit_video_device::log_bitmap(int command, int bpp, bool Skip)
}
}
png_write_bitmap(file, nullptr, m_log_bitmap, 0, nullptr);
util::png_write_bitmap(file, nullptr, m_log_bitmap, 0, nullptr);
if (m_log_json)
{

View File

@ -135,7 +135,6 @@
#include "emu.h"
#include "includes/ssv.h"
#include "render.h"
void ssv_state::drawgfx_line(bitmap_ind16 &bitmap, const rectangle &cliprect, int gfx, uint32_t code, uint32_t color, int flipx, int flipy, int base_sx, int base_sy, int shadow, int realline, int line)

View File

@ -16,7 +16,9 @@
#include "emu.h"
#include "st0020.h"
#include "render.h"
#include "screen.h"
DEFINE_DEVICE_TYPE(ST0020_SPRITES, st0020_device, "st0020", "Seta ST0020 Sprites")

View File

@ -7,10 +7,10 @@
****************************************************************************/
#include "emu.h"
#include "render.h"
#include "includes/tx1.h"
#include "video/resnet.h"
#include "cpu/i86/i86.h"
#include "includes/tx1.h"
#define OBJ_FRAC 16

View File

@ -59,7 +59,11 @@ bgfx_texture* texture_manager::create_png_texture(std::string path, std::string
{
bitmap_argb32 bitmap;
emu_file file(path.c_str(), OPEN_FLAG_READ);
render_load_png(bitmap, file, nullptr, file_name.c_str());
if (file.open(file_name) == osd_file::error::NONE)
{
render_load_png(bitmap, file);
file.close();
}
if (bitmap.width() == 0 || bitmap.height() == 0)
{

View File

@ -359,14 +359,14 @@ void shaders::render_snapshot(IDirect3DSurface9 *surface)
// add two text entries describing the image
std::string text1 = std::string(emulator_info::get_appname()).append(" ").append(emulator_info::get_build_version());
std::string text2 = std::string(machine->system().manufacturer).append(" ").append(machine->system().type.fullname());
png_info pnginfo;
util::png_info pnginfo;
pnginfo.add_text("Software", text1.c_str());
pnginfo.add_text("System", text2.c_str());
// now do the actual work
png_error error = png_write_bitmap(file, &pnginfo, snapshot, 1 << 24, nullptr);
if (error != PNGERR_NONE)
osd_printf_error("Error generating PNG for HLSL snapshot: png_error = %d\n", error);
util::png_error error = util::png_write_bitmap(file, &pnginfo, snapshot, 1 << 24, nullptr);
if (error != util::png_error::NONE)
osd_printf_error("Error generating PNG for HLSL snapshot: png_error = %d\n", std::underlying_type_t<util::png_error>(error));
result = snap_copy_target->UnlockRect();
if (FAILED(result))
@ -721,7 +721,11 @@ int shaders::create_resources()
osd_printf_verbose("Direct3D: Error %08lX during device SetRenderTarget call\n", result);
emu_file file(machine->options().art_path(), OPEN_FLAG_READ);
render_load_png(shadow_bitmap, file, nullptr, options->shadow_mask_texture);
if (file.open(options->shadow_mask_texture) == osd_file::error::NONE)
{
render_load_png(shadow_bitmap, file);
file.close();
}
// experimental: if we have a shadow bitmap, create a texture for it
if (shadow_bitmap.valid())
@ -742,7 +746,11 @@ int shaders::create_resources()
d3d->get_texture_manager()->m_texture_list.push_back(std::move(tex));
}
render_load_png(lut_bitmap, file, nullptr, options->lut_texture);
if (file.open(options->lut_texture) == osd_file::error::NONE)
{
render_load_png(lut_bitmap, file);
file.close();
}
if (lut_bitmap.valid())
{
render_texinfo texture;
@ -761,7 +769,11 @@ int shaders::create_resources()
d3d->get_texture_manager()->m_texture_list.push_back(std::move(tex));
}
render_load_png(ui_lut_bitmap, file, nullptr, options->ui_lut_texture);
if (file.open(options->ui_lut_texture) == osd_file::error::NONE)
{
render_load_png(ui_lut_bitmap, file);
file.close();
}
if (ui_lut_bitmap.valid())
{
render_texinfo texture;

View File

@ -74,7 +74,7 @@ static int generate_png_diff(const std::string& imgfile1, const std::string& img
int width, height, maxwidth;
util::core_file::ptr file;
osd_file::error filerr;
png_error pngerr;
util::png_error pngerr;
int error = 100;
/* open the source image */
@ -86,11 +86,11 @@ static int generate_png_diff(const std::string& imgfile1, const std::string& img
}
/* load the source image */
pngerr = png_read_bitmap(*file, bitmap1);
pngerr = util::png_read_bitmap(*file, bitmap1);
file.reset();
if (pngerr != PNGERR_NONE)
if (pngerr != util::png_error::NONE)
{
printf("Could not read %s (%d)\n", imgfile1.c_str(), pngerr);
printf("Could not read %s (%d)\n", imgfile1.c_str(), int(pngerr));
goto error;
}
@ -103,11 +103,11 @@ static int generate_png_diff(const std::string& imgfile1, const std::string& img
}
/* load the source image */
pngerr = png_read_bitmap(*file, bitmap2);
pngerr = util::png_read_bitmap(*file, bitmap2);
file.reset();
if (pngerr != PNGERR_NONE)
if (pngerr != util::png_error::NONE)
{
printf("Could not read %s (%d)\n", imgfile2.c_str(), pngerr);
printf("Could not read %s (%d)\n", imgfile2.c_str(), int(pngerr));
goto error;
}
@ -176,11 +176,11 @@ static int generate_png_diff(const std::string& imgfile1, const std::string& img
printf("Could not open %s (%d)\n", outfilename.c_str(), int(filerr));
goto error;
}
pngerr = png_write_bitmap(*file, nullptr, finalbitmap, 0, nullptr);
pngerr = util::png_write_bitmap(*file, nullptr, finalbitmap, 0, nullptr);
file.reset();
if (pngerr != PNGERR_NONE)
if (pngerr != util::png_error::NONE)
{
printf("Could not write %s (%d)\n", outfilename.c_str(), pngerr);
printf("Could not write %s (%d)\n", outfilename.c_str(), int(pngerr));
goto error;
}
}

View File

@ -748,7 +748,7 @@ static int compare_screenshots(summary_file *curfile)
/* if that worked, load the file */
if (filerr == osd_file::error::NONE)
{
png_read_bitmap(*file, bitmaps[listnum]);
util::png_read_bitmap(*file, bitmaps[listnum]);
file.reset();
}
}
@ -835,7 +835,7 @@ static int generate_png_diff(const summary_file *curfile, std::string &destdir,
int bitmapcount = 0;
util::core_file::ptr file;
osd_file::error filerr;
png_error pngerr;
util::png_error pngerr;
int error = -1;
int starty;
@ -855,9 +855,9 @@ static int generate_png_diff(const summary_file *curfile, std::string &destdir,
goto error;
/* load the source image */
pngerr = png_read_bitmap(*file, bitmaps[bitmapcount++]);
pngerr = util::png_read_bitmap(*file, bitmaps[bitmapcount++]);
file.reset();
if (pngerr != PNGERR_NONE)
if (pngerr != util::png_error::NONE)
goto error;
}
@ -924,9 +924,9 @@ static int generate_png_diff(const summary_file *curfile, std::string &destdir,
filerr = util::core_file::open(dstfilename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, file);
if (filerr != osd_file::error::NONE)
goto error;
pngerr = png_write_bitmap(*file, nullptr, finalbitmap, 0, nullptr);
pngerr = util::png_write_bitmap(*file, nullptr, finalbitmap, 0, nullptr);
file.reset();
if (pngerr != PNGERR_NONE)
if (pngerr != util::png_error::NONE)
goto error;
/* if we get here, we are error free */