Initial commit

This commit is contained in:
2026-02-02 04:50:13 +01:00
commit 5b11698731
22592 changed files with 7677434 additions and 0 deletions

View File

@@ -0,0 +1,431 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/screen.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/shared/std/containers.h"
#include "common/system.h"
#include "common/list.h"
#include "common/textconsole.h"
#include "graphics/screen.h"
namespace Ultima {
namespace Ultima4 {
Image::Image() : _surface(nullptr), _disposeAfterUse(DisposeAfterUse::NO),
_paletted(false) {
}
Image *Image::create(int w, int h) {
Image *im = new Image();
im->createInternal(w, h, Graphics::PixelFormat::createFormatCLUT8());
return im;
}
Image *Image::create(int w, int h, const Graphics::PixelFormat &format) {
Image *im = new Image();
im->createInternal(w, h, format);
return im;
}
void Image::createInternal(int w, int h, const Graphics::PixelFormat &format) {
_paletted = format.isCLUT8();
_surface = new Graphics::ManagedSurface(w, h, format);
_disposeAfterUse = DisposeAfterUse::YES;
}
void Image::blitFrom(const Graphics::Surface &src) {
_surface->blitFrom(src);
}
Image *Image::createScreenImage() {
Image *screen = new Image();
screen->_surface = g_screen;
screen->_disposeAfterUse = DisposeAfterUse::NO;
screen->_paletted = false;
return screen;
}
Image *Image::duplicate(Image *image, const Graphics::PixelFormat &format) {
bool alphaOn = image->isAlphaOn();
Image *im = create(image->width(), image->height(), format);
if (im->isIndexed())
im->setPaletteFromImage(image);
/* Turn alpha off before blitting to non-screen surfaces */
if (alphaOn)
image->alphaOff();
image->drawOn(im, 0, 0);
if (alphaOn)
image->alphaOn();
im->_backgroundColor = image->_backgroundColor;
return im;
}
Image::~Image() {
if (_disposeAfterUse == DisposeAfterUse::YES)
delete _surface;
}
void Image::setPalette(const byte *colors, unsigned n_colors) {
assertMsg(_paletted, "imageSetPalette called on non-paletted image");
_surface->setPalette(colors, 0, n_colors);
}
void Image::setPaletteFromImage(const Image *src) {
assertMsg(_paletted && src->_paletted, "imageSetPaletteFromImage called on non-indexed image");
uint8 srcPal[Graphics::PALETTE_COUNT * 3];
src->_surface->grabPalette(srcPal, 0, Graphics::PALETTE_COUNT);
_surface->setPalette(srcPal, 0, Graphics::PALETTE_COUNT);
}
RGBA Image::getPaletteColor(int index) {
RGBA color = RGBA(0, 0, 0, 0);
if (_paletted) {
uint8 pal[1 * 3];
_surface->grabPalette(pal, index, 1);
color.r = pal[0];
color.g = pal[1];
color.b = pal[2];
color.a = IM_OPAQUE;
}
return color;
}
RGBA Image::setColor(uint8 r, uint8 g, uint8 b, uint8 a) {
RGBA color = RGBA(r, g, b, a);
return color;
}
bool Image::setFontColor(ColorFG fg, ColorBG bg) {
if (!setFontColorFG(fg)) return false;
if (!setFontColorBG(bg)) return false;
return true;
}
bool Image::setFontColorFG(ColorFG fg) {
switch (fg) {
case FG_GREY:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 153, 153, 153)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 102, 102, 102)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
case FG_BLUE:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 102, 102, 255)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 51, 51, 204)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
case FG_PURPLE:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 255, 102, 255)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 51, 204)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
case FG_GREEN:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 102, 255, 102)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 0, 153, 0)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
case FG_RED:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 255, 102, 102)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 51, 51)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
case FG_YELLOW:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 255, 255, 51)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 153, 51)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 51, 51, 51)) return false;
break;
default:
if (!setPaletteIndex(TEXT_FG_PRIMARY_INDEX, 255, 255, 255)) return false;
if (!setPaletteIndex(TEXT_FG_SECONDARY_INDEX, 204, 204, 204)) return false;
if (!setPaletteIndex(TEXT_FG_SHADOW_INDEX, 68, 68, 68)) return false;
}
return true;
}
bool Image::setFontColorBG(ColorBG bg) {
switch (bg) {
case BG_BRIGHT:
if (!setPaletteIndex(TEXT_BG_INDEX, 0, 0, 102))
return false;
break;
default:
if (!setPaletteIndex(TEXT_BG_INDEX, 0, 0, 0))
return false;
}
return true;
}
bool Image::setPaletteIndex(uint index, uint8 r, uint8 g, uint8 b) {
if (!_paletted)
return false;
const uint8 palette[1 * 3] = {
r, g, b
};
_surface->setPalette(palette, index, 1);
// success
return true;
}
bool Image::getTransparentIndex(uint &index) const {
if (!_paletted || !_surface->hasTransparentColor())
return false;
index = _surface->getTransparentColor();
return true;
}
void Image::initializeToBackgroundColor(RGBA backgroundColor) {
if (_paletted)
error("initializeToBackgroundColor: Not supported");
_backgroundColor = backgroundColor;
fillRect(0, 0, _surface->w, _surface->h, backgroundColor.r,
backgroundColor.g, backgroundColor.b, backgroundColor.a);
}
bool Image::isAlphaOn() const {
return !_paletted;
}
void Image::alphaOn() {
}
void Image::alphaOff() {
}
void Image::putPixel(int x, int y, int r, int g, int b, int a) {
uint32 color = getColor(r, g, b, a);
_surface->setPixel(x, y, color);
}
uint Image::getColor(byte r, byte g, byte b, byte a) {
uint color;
if (_surface->format.bytesPerPixel == 1) {
uint8 pal[256 * 3];
_surface->grabPalette(pal, 0, 256);
for (color = 0; color <= 0xfe; ++color) {
byte rv = pal[(color * 3) + 0];
byte gv = pal[(color * 3) + 1] & 0xff;
byte bv = pal[(color * 3) + 2] & 0xff;
if (r == rv && g == gv && b == bv)
break;
}
return color;
} else {
return _surface->format.ARGBToColor(a, r, g, b);
}
}
void Image::makeBackgroundColorTransparent(int haloSize, int shadowOpacity) {
uint32 bgColor = _surface->format.ARGBToColor(
static_cast<byte>(_backgroundColor.a),
static_cast<byte>(_backgroundColor.r),
static_cast<byte>(_backgroundColor.g),
static_cast<byte>(_backgroundColor.b)
);
performTransparencyHack(bgColor, 1, 0, haloSize, shadowOpacity);
}
void Image::performTransparencyHack(uint colorValue, uint numFrames,
uint currentFrameIndex, uint haloWidth,
uint haloOpacityIncrementByPixelDistance) {
Common::List<Common::Pair<uint, uint> > opaqueXYs;
int x, y;
byte t_r, t_g, t_b;
_surface->format.colorToRGB(colorValue, t_r, t_g, t_b);
int frameHeight = _surface->h / numFrames;
//Min'd so that they never go out of range (>=h)
int top = MIN(_surface->h, (int16)(currentFrameIndex * frameHeight));
int bottom = MIN(_surface->h, (int16)(top + frameHeight));
for (y = top; y < bottom; y++) {
for (x = 0; x < _surface->w; x++) {
uint r, g, b, a;
getPixel(x, y, r, g, b, a);
if (r == t_r &&
g == t_g &&
b == t_b) {
putPixel(x, y, r, g, b, IM_TRANSPARENT);
} else {
putPixel(x, y, r, g, b, a);
if (haloWidth)
opaqueXYs.push_back(Common::Pair<uint, uint>(x, y));
}
}
}
int ox, oy;
for (const auto &xy : opaqueXYs) {
ox = xy.first;
oy = xy.second;
int span = int(haloWidth);
int x_start = MAX(0, ox - span);
int x_finish = MIN(int(_surface->w), ox + span + 1);
for (x = x_start; x < x_finish; ++x) {
int y_start = MAX(int(top), oy - span);
int y_finish = MIN(int(bottom), oy + span + 1);
for (y = y_start; y < y_finish; ++y) {
int divisor = 1 + span * 2 - abs(int(ox - x)) - abs(int(oy - y));
uint r, g, b, a;
getPixel(x, y, r, g, b, a);
if (a != IM_OPAQUE) {
putPixel(x, y, r, g, b, MIN(IM_OPAQUE, a + haloOpacityIncrementByPixelDistance / divisor));
}
}
}
}
}
void Image::setTransparentIndex(uint index) {
if (_paletted)
_surface->setTransparentColor(index);
}
void Image::putPixelIndex(int x, int y, uint index) {
_surface->setPixel(x, y, index);
}
void Image::fillRect(int x, int y, int w, int h, int r, int g, int b, int a) {
uint32 color = getColor(r, g, b, a);
_surface->fillRect(Common::Rect(x, y, x + w, y + h), color);
}
void Image::getPixel(int x, int y, uint &r, uint &g, uint &b, uint &a) const {
uint index;
byte r1, g1, b1, a1;
index = _surface->getPixel(x, y);
if (_surface->format.bytesPerPixel == 1) {
uint8 pal[1 * 3];
_surface->grabPalette(pal, index, 1);
r = pal[0];
g = pal[1];
b = pal[2];
a = IM_OPAQUE;
} else {
_surface->format.colorToARGB(index, a1, r1, g1, b1);
r = r1;
g = g1;
b = b1;
a = a1;
}
}
void Image::getPixelIndex(int x, int y, uint &index) const {
index = _surface->getPixel(x, y);
}
Graphics::ManagedSurface *Image::getSurface(Image *d) const {
if (d)
return d->_surface;
return g_screen;
}
void Image::drawOn(Image *d, int x, int y) const {
Graphics::ManagedSurface *destSurface = getSurface(d);
destSurface->blitFrom(*_surface, Common::Point(x, y));
}
void Image::drawSubRectOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const {
Graphics::ManagedSurface *destSurface = getSurface(d);
Common::Rect srcRect(rx, ry, MIN(rx + rw, (int)_surface->w), MIN(ry + rh, (int)_surface->h));
Common::Point destPos(x, y);
// Handle when the source rect is off the surface
if (srcRect.left < 0) {
destPos.x += -srcRect.left;
srcRect.left = 0;
}
if (srcRect.top < 0) {
destPos.y += -srcRect.top;
srcRect.top = 0;
}
if (srcRect.isValidRect())
// Blit the surface
destSurface->blitFrom(*_surface, srcRect, destPos);
}
void Image::drawSubRectInvertedOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const {
Graphics::ManagedSurface *destSurface = getSurface(d);
int i;
Common::Rect src;
Common::Point destPos;
for (i = 0; i < rh; i++) {
src.left = rx;
src.top = ry + i;
src.right = rx + rw;
src.bottom = ry + i + 1;
destPos.x = x;
destPos.y = y + rh - i - 1;
destSurface->blitFrom(*_surface, src, destPos);
}
}
void Image::dump() {
g_screen->blitFrom(*_surface, Common::Point(0, 0));
g_screen->update();
}
void Image::drawHighlighted() {
RGBA c;
for (int i = 0; i < _surface->h; i++) {
for (int j = 0; j < _surface->w; j++) {
getPixel(j, i, c.r, c.g, c.b, c.a);
putPixel(j, i, 0xff - c.r, 0xff - c.g, 0xff - c.b, c.a);
}
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,270 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_IMAGE_H
#define ULTIMA4_GFX_IMAGE_H
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/gfx/textcolor.h"
#include "graphics/managed_surface.h"
namespace Ultima {
namespace Ultima4 {
typedef Graphics::ManagedSurface *BackendSurface;
#define DARK_GRAY_HALO RGBA(14,15,16,255)
struct RGBA {
RGBA(int red, int green, int blue, int alpha) : r(red), g(green), b(blue), a(alpha) {}
RGBA() : r(0), g(0), b(0), a(255) {}
uint r, g, b, a;
operator uint32() const {
return r | (g << 8) | (b << 16) | (0xff << 24);
}
};
bool operator==(const RGBA &lhs, const RGBA &rhs);
class Image;
struct SubImage : public Common::Rect {
Common::String _name;
Common::String _srcImageName;
};
#define IM_OPAQUE (uint) 255
#define IM_TRANSPARENT 0
/**
* A simple image object that can be drawn and read/written to at the
* pixel level.
* @todo
* <ul>
* <li>drawing methods should be pushed to Drawable subclass</li>
* </ul>
*/
class Image {
private:
Graphics::ManagedSurface *_surface;
DisposeAfterUse::Flag _disposeAfterUse;
bool _paletted;
RGBA _backgroundColor;
Image(); /* use create method to construct images */
// disallow assignments, copy construction
Image(const Image &);
const Image &operator=(const Image &);
Graphics::ManagedSurface *getSurface(Image *d) const;
uint getColor(byte r, byte g, byte b, byte a);
public:
/**
* Creates a new palette based image. Scale is stored to allow drawing
* using U4 (320x200) coordinates, regardless of the actual image scale.
*/
static Image *create(int w, int h);
/**
* Creates a new RGB image. Scale is stored to allow drawing using U4
* (320x200) coordinates, regardless of the actual image scale.
*/
static Image *create(int w, int h, const Graphics::PixelFormat &format);
/**
* Create a special purpose image the represents the whole screen.
*/
static Image *createScreenImage();
/**
* Creates a duplicate of another image
*/
static Image *duplicate(Image *image, const Graphics::PixelFormat &format);
/**
* Frees the image.
*/
~Image();
void createInternal(int w, int h, const Graphics::PixelFormat &format);
/* palette handling */
/**
* Sets the palette
*/
void setPalette(const byte *colors, unsigned n_colors);
/**
* Copies the palette from another image.
*/
void setPaletteFromImage(const Image *src);
bool getTransparentIndex(uint &index) const;
void performTransparencyHack(uint colorValue, uint numFrames, uint currentFrameIndex, uint haloWidth, uint haloOpacityIncrementByPixelDistance);
void setTransparentIndex(uint index);
/**
* Sets the specified font colors
*/
bool setFontColor(ColorFG fg, ColorBG bg);
/**
* Sets the specified font colors
*/
bool setFontColorFG(ColorFG fg);
/**
* Sets the specified font colors
*/
bool setFontColorBG(ColorBG bg);
/**
* Returns the color of the specified palette index
*/
RGBA getPaletteColor(int index); // returns the color of the specified palette index
/**
* Sets the specified palette index to the specified RGB color
*/
bool setPaletteIndex(uint index, uint8 r, uint8 g, uint8 b);
RGBA setColor(uint8 r, uint8 g, uint8 b, uint8 a = IM_OPAQUE);
/* alpha handling */
bool isAlphaOn() const;
void alphaOn();
void alphaOff();
/* Will clear the image to the background color, and set the internal backgroundColor variable */
void initializeToBackgroundColor(RGBA backgroundColor = DARK_GRAY_HALO);
/* Will make the pixels that match the background color disappear, with a blur halo */
void makeBackgroundColorTransparent(int haloSize = 0, int shadowOpacity = 255);
//void finalizeAlphaSurface(RGBA * key = nullptr);
/* writing to image */
/**
* Sets the color of a single pixel.
*/
void putPixel(int x, int y, int r, int g, int b, int a); //TODO Consider using &
/**
* Sets the palette index of a single pixel. If the image is in
* indexed mode, then the index is simply the palette entry number.
* If the image is RGB, it is a packed RGB triplet.
*/
void putPixelIndex(int x, int y, uint index);
/**
* Fills a rectangle in the image with a given color.
*/
void fillRect(int x, int y, int w, int h, int r, int g, int b, int a = IM_OPAQUE);
void blitFrom(const Graphics::Surface &src);
/* reading from image */
/**
* Gets the color of a single pixel.
*/
void getPixel(int x, int y, uint &r, uint &g, uint &b, uint &a) const;
/**
* Gets the palette index of a single pixel. If the image is in
* indexed mode, then the index is simply the palette entry number.
* If the image is RGB, it is a packed RGB triplet.
*/
void getPixelIndex(int x, int y, uint &index) const;
/* image drawing methods */
/**
* Draws the entire image onto the screen at the given offset.
*/
void draw(int x, int y) const {
drawOn(nullptr, x, y);
}
/**
* Draws a piece of the image onto the screen at the given offset.
* The area of the image to draw is defined by the rectangle rx, ry,
* rw, rh.
*/
void drawSubRect(int x, int y, int rx, int ry, int rw, int rh) const {
drawSubRectOn(nullptr, x, y, rx, ry, rw, rh);
}
/**
* Draws a piece of the image onto the screen at the given offset, inverted.
* The area of the image to draw is defined by the rectangle rx, ry,
* rw, rh.
*/
void drawSubRectInverted(int x, int y, int rx, int ry, int rw, int rh) const {
drawSubRectInvertedOn(nullptr, x, y, rx, ry, rw, rh);
}
/* image drawing methods for drawing onto another image instead of the screen */
/**
* Draws the image onto another image.
*/
void drawOn(Image *d, int x, int y) const;
/**
* Draws a piece of the image onto another image.
*/
void drawSubRectOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const;
/**
* Draws a piece of the image onto another image, inverted.
*/
void drawSubRectInvertedOn(Image *d, int x, int y, int rx, int ry, int rw, int rh) const;
int width() const {
return _surface->w;
}
int height() const {
return _surface->h;
}
Graphics::PixelFormat format() const {
return _surface->format;
}
bool isIndexed() const {
return _paletted;
}
BackendSurface getSurface() {
return _surface;
}
void drawHighlighted();
/**
* Debug method that dumps a given image directly to the screen
* and does a screen update. Useful for validating images are correct
*/
void dump();
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,107 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/gfx/imageloader.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
namespace Ultima {
namespace Ultima4 {
/*-------------------------------------------------------------------*/
U4ImageDecoder::U4ImageDecoder(int width, int height, int bpp) : _palette(0) {
_width = width;
_height = height;
_bpp = bpp;
_surface = nullptr;
}
U4ImageDecoder::~U4ImageDecoder() {
destroy();
}
void U4ImageDecoder::destroy() {
if (_surface) {
_surface->free();
delete _surface;
_surface = nullptr;
}
_palette.clear();
}
void U4ImageDecoder::setFromRawData(const byte *rawData) {
int x, y;
switch (_bpp) {
case 32:
case 24:
case 8:
_surface->copyRectToSurface(rawData, (_width * _bpp) / 8, 0, 0, _width, _height);
break;
case 4:
for (y = 0; y < _height; y++) {
for (x = 0; x < _width; x += 2) {
_surface->setPixel(x, y, rawData[(y * _width + x) / 2] >> 4);
_surface->setPixel(x + 1, y, rawData[(y * _width + x) / 2] & 0x0f);
}
}
break;
case 1:
for (y = 0; y < _height; y++) {
for (x = 0; x < _width; x += 8) {
_surface->setPixel(x + 0, y, (rawData[(y * _width + x) / 8] >> 7) & 0x01);
_surface->setPixel(x + 1, y, (rawData[(y * _width + x) / 8] >> 6) & 0x01);
_surface->setPixel(x + 2, y, (rawData[(y * _width + x) / 8] >> 5) & 0x01);
_surface->setPixel(x + 3, y, (rawData[(y * _width + x) / 8] >> 4) & 0x01);
_surface->setPixel(x + 4, y, (rawData[(y * _width + x) / 8] >> 3) & 0x01);
_surface->setPixel(x + 5, y, (rawData[(y * _width + x) / 8] >> 2) & 0x01);
_surface->setPixel(x + 6, y, (rawData[(y * _width + x) / 8] >> 1) & 0x01);
_surface->setPixel(x + 7, y, (rawData[(y * _width + x) / 8] >> 0) & 0x01);
}
}
break;
default:
error("invalid bits-per-pixel (bpp): %d", _bpp);
}
}
Graphics::PixelFormat U4ImageDecoder::getPixelFormatForBpp() const {
switch (_bpp) {
case 1:
case 4:
case 8:
return Graphics::PixelFormat::createFormatCLUT8();
case 24:
return Graphics::PixelFormat::createFormatRGB24();
case 32:
return Graphics::PixelFormat::createFormatRGBA32();
default:
error("invalid bits-per-pixel (bpp): %d", _bpp);
}
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,68 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_IMAGELOADER_H
#define ULTIMA4_GFX_IMAGELOADER_H
#include "graphics/palette.h"
#include "graphics/pixelformat.h"
#include "image/image_decoder.h"
namespace Common {
class SeekableReadStream;
}
namespace Ultima {
namespace Ultima4 {
/**
* A common base for all Ultima 4 image loaders.
*/
class U4ImageDecoder : public ::Image::ImageDecoder {
public:
U4ImageDecoder(int width, int height, int bpp);
virtual ~U4ImageDecoder();
// ImageDecoder API
void destroy() override;
const Graphics::Surface *getSurface() const override { return _surface; }
const Graphics::Palette &getPalette() const override { return _palette; }
protected:
Graphics::Surface *_surface;
Graphics::Palette _palette;
int _width, _height, _bpp;
/**
* Fill in the image pixel data from an uncompressed string of bytes.
*/
void setFromRawData(const byte *rawData);
/**
* Get the expected pixel format based on the value of _bpp.
*/
Graphics::PixelFormat getPixelFormatForBpp() const;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,87 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/gfx/imageloader_fmtowns.h"
#include "ultima/ultima4/gfx/imageloader_u4.h"
#include "common/stream.h"
#include "common/textconsole.h"
#include "graphics/surface.h"
namespace Ultima {
namespace Ultima4 {
bool FMTOWNSImageDecoder::loadStream(Common::SeekableReadStream &stream) {
destroy();
if (_width == -1 || _height == -1 || _bpp == -1) {
error("dimensions not set for fmtowns image");
}
assertMsg((_bpp == 16) | (_bpp == 4), "invalid bpp: %d", _bpp);
int rawLen = stream.size() - _offset;
int requiredLength = (_width * _height * _bpp / 8);
if (rawLen < requiredLength) {
warning("FMTOWNS Image of size %d does not fit anticipated size %d", rawLen, requiredLength);
return false;
}
stream.seek(_offset, 0);
byte *raw = (byte *) malloc(rawLen);
stream.read(raw, rawLen);
if (_bpp == 4) {
_surface = new Graphics::Surface();
_surface->create(_width, _height, Graphics::PixelFormat::createFormatCLUT8());
setFromRawData(raw);
U4PaletteLoader pal;
_palette.resize(16, false);
_palette.set(pal.loadEgaPalette(), 0, 16);
}
if (_bpp == 16) {
_surface = new Graphics::Surface();
_surface->create(_width, _height, Graphics::PixelFormat(2, 5, 5, 5, 1, 5, 10, 0, 15));
uint16 *dst = (uint16 *)_surface->getPixels();
const uint16 *src = (const uint16 *)raw;
for (int y = 0; y < _height; y++) {
for (int x = 0; x < _width; x++) {
dst[x] = FROM_LE_16(*src++) ^ 0x8000;
}
dst = (uint16 *)((uint8 *)dst + _surface->pitch);
}
}
free(raw);
return true;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,43 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_IMAGELOADER_FMTOWNS_H_
#define ULTIMA4_GFX_IMAGELOADER_FMTOWNS_H_
#include "ultima/ultima4/gfx/imageloader.h"
namespace Ultima {
namespace Ultima4 {
class FMTOWNSImageDecoder : public U4ImageDecoder {
protected:
int _offset;
public:
FMTOWNSImageDecoder(int width, int height, int bpp, int offset)
: U4ImageDecoder(width, height, bpp), _offset(offset) {}
bool loadStream(Common::SeekableReadStream &stream) override;
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,230 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/utils.h"
#include "ultima/ultima4/core/lzw/u4decode.h"
#include "ultima/ultima4/gfx/imageloader_u4.h"
#include "ultima/ultima4/filesys/rle.h"
#include "common/file.h"
#include "graphics/surface.h"
#include "graphics/palette.h"
namespace Ultima {
namespace Ultima4 {
const byte U4PaletteLoader::_bwPalette[2 * 3] = {
0, 0, 0,
255, 255, 255
};
byte *U4PaletteLoader::_egaPalette = nullptr;
byte *U4PaletteLoader::_vgaPalette = nullptr;
bool U4RawImageDecoder::loadStream(Common::SeekableReadStream &stream) {
if (_width == -1 || _height == -1 || _bpp == -1) {
error("dimensions not set for u4raw image");
}
assertMsg(_bpp == 1 || _bpp == 4 || _bpp == 8 || _bpp == 24 || _bpp == 32, "invalid bpp: %d", _bpp);
long rawLen = stream.size();
byte *raw = (byte *)malloc(rawLen);
stream.read(raw, rawLen);
long requiredLength = (_width * _height * _bpp / 8);
if (rawLen < requiredLength) {
if (raw)
free(raw);
warning("u4Raw Image of size %ld does not fit anticipated size %ld", rawLen, requiredLength);
return false;
}
_surface = new Graphics::Surface();
_surface->create(_width, _height, getPixelFormatForBpp());
U4PaletteLoader paletteLoader;
if (_bpp == 8) {
_palette.resize(256, false);
_palette.set(paletteLoader.loadVgaPalette(), 0, 256);
} else if (_bpp == 4) {
_palette.resize(16, false);
_palette.set(paletteLoader.loadEgaPalette(), 0, 16);
} else if (_bpp == 1) {
_palette.resize(2, false);
_palette.set(paletteLoader.loadBWPalette(), 0, 2);
}
setFromRawData(raw);
free(raw);
return true;
}
/**
* Loads in the rle-compressed image and apply the standard U4 16 or
* 256 color palette.
*/
bool U4RleImageDecoder::loadStream(Common::SeekableReadStream &stream) {
if (_width == -1 || _height == -1 || _bpp == -1) {
error("dimensions not set for u4rle image");
}
assertMsg(_bpp == 1 || _bpp == 4 || _bpp == 8 || _bpp == 24 || _bpp == 32, "invalid bpp: %d", _bpp);
long compressedLen = stream.size();
byte *compressed = (byte *) malloc(compressedLen);
stream.read(compressed, compressedLen);
byte *raw = nullptr;
long rawLen = rleDecompressMemory(compressed, compressedLen, (void **) &raw);
free(compressed);
if (rawLen != (_width * _height * _bpp / 8)) {
if (raw)
free(raw);
return false;
}
_surface = new Graphics::Surface();
_surface->create(_width, _height, getPixelFormatForBpp());
U4PaletteLoader paletteLoader;
if (_bpp == 8) {
_palette.resize(256, false);
_palette.set(paletteLoader.loadVgaPalette(), 0, 256);
} else if (_bpp == 4) {
_palette.resize(16, false);
_palette.set(paletteLoader.loadEgaPalette(), 0, 16);
} else if (_bpp == 1) {
_palette.resize(2, false);
_palette.set(paletteLoader.loadBWPalette(), 0, 2);
}
setFromRawData(raw);
free(raw);
return true;
}
/**
* Loads in the lzw-compressed image and apply the standard U4 16 or
* 256 color palette.
*/
bool U4LzwImageDecoder::loadStream(Common::SeekableReadStream &stream) {
if (_width == -1 || _height == -1 || _bpp == -1) {
error("dimensions not set for u4lzw image");
}
assertMsg(_bpp == 1 || _bpp == 4 || _bpp == 8 || _bpp == 24 || _bpp == 32, "invalid bpp: %d", _bpp);
long compressedLen = stream.size();
byte *compressed = (byte *) malloc(compressedLen);
stream.read(compressed, compressedLen);
byte *raw = nullptr;
long rawLen = LZW::decompress_u4_memory(compressed, compressedLen, (void **) &raw);
free(compressed);
if (rawLen != (_width * _height * _bpp / 8)) {
if (raw)
free(raw);
return false;
}
_surface = new Graphics::Surface();
_surface->create(_width, _height, getPixelFormatForBpp());
U4PaletteLoader paletteLoader;
if (_bpp == 8) {
_palette.resize(256, false);
_palette.set(paletteLoader.loadVgaPalette(), 0, 256);
} else if (_bpp == 4) {
_palette.resize(16, false);
_palette.set(paletteLoader.loadEgaPalette(), 0, 16);
} else if (_bpp == 1) {
_palette.resize(2, false);
_palette.set(paletteLoader.loadBWPalette(), 0, 2);
}
setFromRawData(raw);
free(raw);
return true;
}
/**
* Loads a simple black & white palette
*/
const byte *U4PaletteLoader::loadBWPalette() {
return _bwPalette;
}
/**
* Loads the basic EGA palette from egaPalette.xml
*/
const byte *U4PaletteLoader::loadEgaPalette() {
if (_egaPalette == nullptr) {
int index = 0;
const Config *config = Config::getInstance();
_egaPalette = new byte[16 * 3];
Std::vector<ConfigElement> paletteConf = config->getElement("egaPalette").getChildren();
for (const auto &i : paletteConf) {
if (i.getName() != "color")
continue;
_egaPalette[index++] = i.getInt("red");
_egaPalette[index++] = i.getInt("green");
_egaPalette[index++] = i.getInt("blue");
}
}
return _egaPalette;
}
/**
* Load the 256 color VGA palette from a file.
*/
const byte *U4PaletteLoader::loadVgaPalette() {
if (_vgaPalette == nullptr) {
Common::File pal;
if (!pal.open("u4vga.pal"))
return nullptr;
_vgaPalette = new byte[256 * 3];
pal.read(_vgaPalette, 256 * 3);
for (int i = 0; i < 256 * 3; i++) {
_vgaPalette[i] = PALETTE_6BIT_TO_8BIT(_vgaPalette[i]);
}
}
return _vgaPalette;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,86 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_IMAGELOADER_U4_H
#define ULTIMA4_GFX_IMAGELOADER_U4_H
#include "ultima/ultima4/gfx/imageloader.h"
namespace Ultima {
namespace Ultima4 {
/**
* Decoder for U4 raw images. Raw images are just an uncompressed
* stream of pixel data with no palette information (e.g. shapes.ega,
* charset.ega). This decoder handles the original 4-bit images, as
* well as the 8-bit VGA upgrade images.
*/
class U4RawImageDecoder : public U4ImageDecoder {
public:
U4RawImageDecoder(int width, int height, int bpp)
: U4ImageDecoder(width, height, bpp) {}
bool loadStream(Common::SeekableReadStream &stream) override;
};
/**
* Decoder for U4 images with RLE compression. Like raw images, the
* data is just a stream of pixel data with no palette information
* (e.g. start.ega, rune_*.ega). This decoder handles the original
* 4-bit images, as well as the 8-bit VGA upgrade images.
*/
class U4RleImageDecoder : public U4ImageDecoder {
public:
U4RleImageDecoder(int width, int height, int bpp)
: U4ImageDecoder(width, height, bpp) {}
bool loadStream(Common::SeekableReadStream &stream) override;
};
/**
* Decoder for U4 images with LZW compression. Like raw images, the
* data is just a stream of pixel data with no palette information
* (e.g. title.ega, tree.ega). This decoder handles the original 4-bit
* images, as well as the 8-bit VGA upgrade images.
*/
class U4LzwImageDecoder : public U4ImageDecoder {
public:
U4LzwImageDecoder(int width, int height, int bpp)
: U4ImageDecoder(width, height, bpp) {}
bool loadStream(Common::SeekableReadStream &stream) override;
};
class U4PaletteLoader {
static const byte _bwPalette[2 * 3];
static byte *_egaPalette;
static byte *_vgaPalette;
public:
const byte *loadBWPalette();
const byte *loadEgaPalette();
const byte *loadVgaPalette();
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,733 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/imageloader_u4.h"
#include "ultima/ultima4/gfx/imageloader_fmtowns.h"
#include "ultima/ultima4/gfx/imagemgr.h"
#include "ultima/ultima4/controllers/intro_controller.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/settings.h"
#include "ultima/ultima4/ultima4.h"
#include "image/png.h"
namespace Ultima {
namespace Ultima4 {
bool ImageInfo::hasBlackBackground() {
return this->_filetype == "image/x-u4raw";
}
class ImageSet {
public:
~ImageSet();
Common::String _name;
Common::String _location;
Common::String _extends;
Common::HashMap<Common::String, ImageInfo *> _info;
};
ImageMgr *ImageMgr::_instance = nullptr;
ImageMgr *ImageMgr::getInstance() {
if (_instance == nullptr) {
_instance = new ImageMgr();
_instance->init();
}
return _instance;
}
void ImageMgr::destroy() {
if (_instance != nullptr) {
delete _instance;
_instance = nullptr;
}
}
ImageMgr::ImageMgr() : _baseSet(nullptr), _abyssData(nullptr) {
settings.addObserver(this);
}
ImageMgr::~ImageMgr() {
settings.deleteObserver(this);
for (auto &i : _imageSets)
delete i._value;
delete[] _abyssData;
}
void ImageMgr::init() {
/*
* register the "screen" image representing the entire screen
*/
Image *screen = Image::createScreenImage();
_screenInfo._name = "screen";
_screenInfo._filename = "";
_screenInfo._width = screen->width();
_screenInfo._height = screen->height();
_screenInfo._depth = 0;
_screenInfo._prescale = 0;
_screenInfo._filetype = "";
_screenInfo._tiles = 0;
_screenInfo._introOnly = false;
_screenInfo._transparentIndex = -1;
_screenInfo._xu4Graphic = false;
_screenInfo._fixup = FIXUP_NONE;
_screenInfo._image = screen;
/*
* register all the images declared in the config files
*/
const Config *config = Config::getInstance();
Std::vector<ConfigElement> graphicsConf = config->getElement("graphics").getChildren();
for (const auto &conf : graphicsConf) {
if (conf.getName() == "imageset") {
ImageSet *set = loadImageSetFromConf(conf);
_imageSets[set->_name] = set;
// all image sets include the "screen" image
set->_info[_screenInfo._name] = &_screenInfo;
}
}
_imageSetNames.clear();
for (const auto &set : _imageSets)
_imageSetNames.push_back(set._key);
update(&settings);
}
ImageSet *ImageMgr::loadImageSetFromConf(const ConfigElement &conf) {
ImageSet *set;
set = new ImageSet();
set->_name = conf.getString("name");
set->_location = conf.getString("location");
set->_extends = conf.getString("extends");
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "image") {
ImageInfo *info = loadImageInfoFromConf(i);
if (set->_info.contains(info->_name))
delete set->_info[info->_name];
set->_info[info->_name] = info;
}
}
return set;
}
ImageInfo *ImageMgr::loadImageInfoFromConf(const ConfigElement &conf) {
ImageInfo *info;
static const char *const fixupEnumStrings[] = { "none", "intro", "abyss", "abacus", "dungns", "blackTransparencyHack", "fmtownsscreen", nullptr };
info = new ImageInfo();
info->_name = conf.getString("name");
info->_filename = conf.getString("filename");
info->_width = conf.getInt("width", -1);
info->_height = conf.getInt("height", -1);
info->_depth = conf.getInt("depth", -1);
info->_prescale = conf.getInt("prescale");
info->_filetype = conf.getString("filetype");
info->_tiles = conf.getInt("tiles");
info->_introOnly = conf.getBool("introOnly");
info->_transparentIndex = conf.getInt("transparentIndex", -1);
info->_xu4Graphic = conf.getBool("xu4Graphic");
info->_fixup = static_cast<ImageFixup>(conf.getEnum("fixup", fixupEnumStrings));
info->_image = nullptr;
Std::vector<ConfigElement> children = conf.getChildren();
for (const auto &i : children) {
if (i.getName() == "subimage") {
SubImage *subimage = loadSubImageFromConf(info, i);
info->_subImages[subimage->_name] = subimage;
}
}
return info;
}
SubImage *ImageMgr::loadSubImageFromConf(const ImageInfo *info, const ConfigElement &conf) {
SubImage *subimage;
static int x = 0,
y = 0,
last_width = 0,
last_height = 0;
subimage = new SubImage();
subimage->_name = conf.getString("name");
subimage->setWidth(conf.getInt("width"));
subimage->setHeight(conf.getInt("height"));
subimage->_srcImageName = info->_name;
if (conf.exists("x") && conf.exists("y")) {
x = conf.getInt("x");
y = conf.getInt("y");
subimage->moveTo(x, y);
} else {
// Automatically increment our position through the base image
x += last_width;
if (x >= last_width) {
x = 0;
y += last_height;
}
subimage->moveTo(x, y);
}
// "remember" the width and height of this subimage
last_width = subimage->width();
last_height = subimage->height();
return subimage;
}
void ImageMgr::fixupIntro(Image *im, int prescale) {
const byte *sigData;
int i, x, y;
bool alpha = im->isAlphaOn();
RGBA color;
sigData = g_intro->getSigData();
im->alphaOff();
if (settings._videoType != "VGA-ALLPNG" && settings._videoType != "new") {
/* ----------------------------
* update the position of "and"
* ---------------------------- */
im->drawSubRectOn(im, 148 * prescale, 17 * prescale,
153 * prescale,
17 * prescale,
11 * prescale,
4 * prescale);
im->drawSubRectOn(im, 159 * prescale, 17 * prescale,
165 * prescale,
18 * prescale,
1 * prescale,
4 * prescale);
im->drawSubRectOn(im, 160 * prescale, 17 * prescale,
164 * prescale,
17 * prescale,
16 * prescale,
4 * prescale);
/* ---------------------------------------------
* update the position of "Origin Systems, Inc."
* --------------------------------------------- */
im->drawSubRectOn(im, 86 * prescale, 21 * prescale,
88 * prescale,
21 * prescale,
114 * prescale,
9 * prescale);
im->drawSubRectOn(im, 199 * prescale, 21 * prescale,
202 * prescale,
21 * prescale,
6 * prescale,
9 * prescale);
im->drawSubRectOn(im, 207 * prescale, 21 * prescale,
208 * prescale,
21 * prescale,
28 * prescale,
9 * prescale);
/* ---------------------------------------------
* update the position of "Ultima IV"
* --------------------------------------------- */
// move this *prior* to moving "present"
im->drawSubRectOn(im, 59 * prescale, 33 * prescale,
61 * prescale,
33 * prescale,
204 * prescale,
46 * prescale);
/* ---------------------------------------------
* update the position of "Quest of the Avatar"
* --------------------------------------------- */
im->drawSubRectOn(im, 69 * prescale, 80 * prescale, // quEst
70 * prescale,
80 * prescale,
11 * prescale,
13 * prescale);
im->drawSubRectOn(im, 82 * prescale, 80 * prescale, // queST
84 * prescale,
80 * prescale,
27 * prescale,
13 * prescale);
im->drawSubRectOn(im, 131 * prescale, 80 * prescale, // oF
132 * prescale,
80 * prescale,
11 * prescale,
13 * prescale);
im->drawSubRectOn(im, 150 * prescale, 80 * prescale, // THE
149 * prescale,
80 * prescale,
40 * prescale,
13 * prescale);
im->drawSubRectOn(im, 166 * prescale, 80 * prescale, // tHe
165 * prescale,
80 * prescale,
11 * prescale,
13 * prescale);
im->drawSubRectOn(im, 200 * prescale, 80 * prescale, // AVATAR
201 * prescale,
80 * prescale,
81 * prescale,
13 * prescale);
im->drawSubRectOn(im, 227 * prescale, 80 * prescale, // avAtar
228 * prescale,
80 * prescale,
11 * prescale,
13 * prescale);
}
/* -----------------------------------------------------------------------------
* copy "present" to new location between "Origin Systems, Inc." and "Ultima IV"
* ----------------------------------------------------------------------------- */
// do this *after* moving "Ultima IV"
im->drawSubRectOn(im, 132 * prescale, 33 * prescale,
135 * prescale,
0 * prescale,
56 * prescale,
5 * prescale);
if (alpha) {
im->alphaOn();
}
/* ----------------------------
* erase the original "present"
* ---------------------------- */
im->fillRect(135 * prescale, 0 * prescale, 56 * prescale, 5 * prescale, 0, 0, 0);
/* -------------------------
* update the colors for VGA
* ------------------------- */
if (settings._videoType == "VGA") {
ImageInfo *borderInfo = imageMgr->get(BKGD_BORDERS, true);
// ImageInfo *charsetInfo = imageMgr->get(BKGD_CHARSET);
if (!borderInfo)
error("ERROR 1001: Unable to load the \"%s\" data file", BKGD_BORDERS);
delete borderInfo->_image;
borderInfo->_image = nullptr;
borderInfo = imageMgr->get(BKGD_BORDERS, true);
im->setPaletteFromImage(borderInfo->_image);
// update the color of "and" and "present"
(void)im->setPaletteIndex(15, 226, 226, 255);
// update the color of "Origin Systems, Inc."
(void)im->setPaletteIndex(9, 129, 129, 255);
#ifdef TODO
borderInfo->_image->save("border.png");
#endif
// update the border appearance
borderInfo->_image->alphaOff();
borderInfo->_image->drawSubRectOn(im, 0, 96, 0, 0, 16, 56);
for (i = 0; i < 9; i++) {
borderInfo->_image->drawSubRectOn(im, 16 + (i * 32), 96, 144, 0, 48, 48);
}
im->drawSubRectInvertedOn(im, 0, 144, 0, 104, 320, 40);
im->drawSubRectOn(im, 0, 184, 0, 96, 320, 8);
borderInfo->_image->alphaOn();
delete borderInfo->_image;
borderInfo->_image = nullptr;
}
/* -----------------------------
* draw "Lord British" signature
* ----------------------------- */
color = im->setColor(0, 255, 255); // cyan for EGA
int blue[16] = {255, 250, 226, 226, 210, 194, 161, 161,
129, 97, 97, 64, 64, 32, 32, 0
};
i = 0;
while (sigData[i] != 0) {
/* (x/y) are unscaled coordinates, i.e. in 320x200 */
x = sigData[i] + 0x14;
y = 0xBF - sigData[i + 1];
if (settings._videoType != "EGA") {
// yellow gradient
color = im->setColor(255, (y == 1 ? 250 : 255), blue[y]);
}
im->fillRect(x * prescale, y * prescale,
2 * prescale, prescale,
color.r, color.g, color.b);
i += 2;
}
/* --------------------------------------------------------------
* draw the red line between "Origin Systems, Inc." and "present"
* -------------------------------------------------------------- */
/* we're still working with an unscaled surface */
if (settings._videoType != "EGA") {
color = im->setColor(0, 0, 161); // dark blue
} else {
color = im->setColor(128, 0, 0); // dark red for EGA
}
for (i = 84; i < 236; i++) // 152 px wide
im->fillRect(i * prescale, 31 * prescale,
prescale, prescale,
color.r, color.g, color.b);
}
void ImageMgr::fixupAbyssVision(Image *im, int prescale) {
// Ignore fixups for xu4 PNG images - they're already correct
if (im->isIndexed())
return;
/*
* Each VGA vision components must be XORed with all the previous
* vision components to get the actual image.
*/
if (_abyssData) {
for (int y = 0; y < im->height(); y++) {
for (int x = 0; x < im->width(); x++) {
uint index;
im->getPixelIndex(x, y, index);
index ^= _abyssData[y * im->width() + x];
im->putPixelIndex(x, y, index);
}
}
} else {
_abyssData = new uint[im->width() * im->height()];
}
for (int y = 0; y < im->height(); y++) {
for (int x = 0; x < im->width(); x++) {
uint index;
im->getPixelIndex(x, y, index);
_abyssData[y * im->width() + x] = index;
}
}
}
void ImageMgr::fixupAbacus(Image *im, int prescale) {
/*
* surround each bead with a row green pixels to avoid artifacts
* when scaling
*/
im->fillRect(7 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */
im->fillRect(16 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */
im->fillRect(8 * prescale, 186 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */
im->fillRect(8 * prescale, 199 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */
im->fillRect(23 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */
im->fillRect(32 * prescale, 186 * prescale, prescale, 14 * prescale, 0, 255, 80); /* green */
im->fillRect(24 * prescale, 186 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */
im->fillRect(24 * prescale, 199 * prescale, prescale * 8, prescale, 0, 255, 80); /* green */
}
void ImageMgr::fixupDungNS(Image *im, int prescale) {
for (int y = 0; y < im->height(); y++) {
for (int x = 0; x < im->width(); x++) {
uint index;
im->getPixelIndex(x, y, index);
if (index == 1)
im->putPixelIndex(x, y, 2);
else if (index == 2)
im->putPixelIndex(x, y, 1);
}
}
}
void ImageMgr::fixupFMTowns(Image *im, int prescale) {
for (int y = 20; y < im->height(); y++) {
for (int x = 0; x < im->width(); x++) {
uint index;
im->getPixelIndex(x, y, index);
im->putPixelIndex(x, y - 20, index);
}
}
}
ImageSet *ImageMgr::getSet(const Common::String &setname) {
Common::HashMap<Common::String, ImageSet *>::iterator i = _imageSets.find(setname);
if (i != _imageSets.end())
return i->_value;
else
return nullptr;
}
ImageInfo *ImageMgr::getInfo(const Common::String &name) {
return getInfoFromSet(name, _baseSet);
}
ImageInfo *ImageMgr::getInfoFromSet(const Common::String &name, ImageSet *imageset) {
if (!imageset)
return nullptr;
/* if the image set contains the image we want, AND IT EXISTS we are done */
Common::HashMap<Common::String, ImageInfo *>::iterator i = imageset->_info.find(name);
if (i != imageset->_info.end())
if (imageExists(i->_value))
return i->_value;
/* otherwise if this image set extends another, check the base image set */
while (imageset->_extends != "") {
imageset = getSet(imageset->_extends);
return getInfoFromSet(name, imageset);
}
//warning("Searched recursively from imageset %s through to %s and couldn't find %s", baseSet->name.c_str(), imageset->name.c_str(), name.c_str());
return nullptr;
}
Common::String ImageMgr::guessFileType(const Common::String &filename) {
if (filename.size() >= 4 && filename.hasSuffixIgnoreCase(".png")) {
return "image/png";
} else {
return "";
}
}
bool ImageMgr::imageExists(ImageInfo *info) {
if (info->_filename.empty()) //If it is an abstract image like "screen"
return true;
Common::File *file = getImageFile(info);
if (file) {
delete file;
return true;
}
return false;
}
Common::File *ImageMgr::getImageFile(ImageInfo *info) {
Common::String filename = info->_filename;
if (filename.empty())
return nullptr;
Common::File *file = new Common::File();
if (!info->_xu4Graphic) {
// It's a file in the game folder
if (file->open(Common::Path(filename)))
return file;
}
if (file->open(Common::Path("data/graphics/").joinInPlace(filename)))
return file;
delete file;
return nullptr;
}
ImageInfo *ImageMgr::get(const Common::String &name, bool returnUnscaled) {
ImageInfo *info = getInfo(name);
if (!info)
return nullptr;
/* return if already loaded */
if (info->_image != nullptr)
return info;
Common::File *file = getImageFile(info);
Image *unscaled = nullptr;
if (file) {
if (info->_filetype.empty())
info->_filetype = guessFileType(info->_filename);
Common::String filetype = info->_filetype;
::Image::ImageDecoder *decoder = createDecoder(filetype, info->_width, info->_height, info->_depth);
if (decoder == nullptr) {
warning("can't find loader to load image \"%s\" with type \"%s\"", info->_filename.c_str(), filetype.c_str());
} else {
if (!decoder->loadStream(*file)) {
warning("can't load image \"%s\" with type \"%s\"", info->_filename.c_str(), filetype.c_str());
} else {
const Graphics::Surface *surface = decoder->getSurface();
unscaled = Image::create(surface->w, surface->h, surface->format);
unscaled->blitFrom(*surface);
const Graphics::Palette &pal = decoder->getPalette();
if (!pal.empty()) {
unscaled->setPalette(pal.data(), pal.size());
}
if (info->_width == -1) {
// Write in the values for later use.
info->_width = unscaled->width();
info->_height = unscaled->height();
// ### info->depth = ???
}
}
delete decoder;
}
delete file;
} else {
warning("Failed to open file %s for reading.", info->_filename.c_str());
return nullptr;
}
if (unscaled == nullptr)
return nullptr;
if (info->_transparentIndex != -1)
unscaled->setTransparentIndex(info->_transparentIndex);
if (info->_prescale == 0)
info->_prescale = 1;
/*
* fixup the image before scaling it
*/
switch (info->_fixup) {
case FIXUP_NONE:
break;
case FIXUP_INTRO:
fixupIntro(unscaled, info->_prescale);
break;
case FIXUP_ABYSS:
fixupAbyssVision(unscaled, info->_prescale);
break;
case FIXUP_ABACUS:
fixupAbacus(unscaled, info->_prescale);
break;
case FIXUP_DUNGNS:
fixupDungNS(unscaled, info->_prescale);
break;
case FIXUP_FMTOWNSSCREEN:
fixupFMTowns(unscaled, info->_prescale);
break;
case FIXUP_BLACKTRANSPARENCYHACK:
//Apply transparency shadow hack to ultima4 ega and vga upgrade classic graphics.
if (Settings::getInstance()._enhancements && Settings::getInstance()._enhancementsOptions._u4TileTransparencyHack) {
// TODO: Pick a more optimal pixel format?
Image *unscaled_original = unscaled;
unscaled = Image::duplicate(unscaled, Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0));
delete unscaled_original;
int transparency_shadow_size = Settings::getInstance()._enhancementsOptions._u4TrileTransparencyHackShadowBreadth;
int black_index = 0;
int opacity = Settings::getInstance()._enhancementsOptions._u4TileTransparencyHackPixelShadowOpacity;
int frames = info->_tiles;
for (int f = 0; f < frames; ++f)
unscaled->performTransparencyHack(black_index, frames, f, transparency_shadow_size, opacity);
}
break;
}
if (returnUnscaled) {
info->_image = unscaled;
return info;
}
int imageScale = settings._scale;
if ((settings._scale % info->_prescale) != 0) {
int orig_scale = settings._scale;
settings._scale = info->_prescale;
settings.write();
error("image %s is prescaled to an incompatible size: %d\nResetting the scale to %d. Sorry about the inconvenience, please restart.", info->_filename.c_str(), orig_scale, settings._scale);
}
imageScale /= info->_prescale;
info->_image = g_screen->screenScale(unscaled, imageScale, info->_tiles, 1);
delete unscaled;
return info;
}
SubImage *ImageMgr::getSubImage(const Common::String &name) {
Common::String setname;
ImageSet *set = _baseSet;
while (set != nullptr) {
for (auto &i : set->_info) {
ImageInfo *info = (ImageInfo *) i._value;
Common::HashMap<Common::String, SubImage *>::iterator j = info->_subImages.find(name);
if (j != info->_subImages.end())
return j->_value;
}
set = getSet(set->_extends);
}
return nullptr;
}
void ImageMgr::freeIntroBackgrounds() {
for (const auto &i : _imageSets) {
ImageSet *set = i._value;
for (auto &j : set->_info) {
ImageInfo *info = j._value;
if (info->_image != nullptr && info->_introOnly) {
delete info->_image;
info->_image = nullptr;
}
}
}
}
const Std::vector<Common::String> &ImageMgr::getSetNames() {
return _imageSetNames;
}
void ImageMgr::update(Settings *newSettings) {
Common::String setname;
setname = newSettings->_videoType;
_baseSet = getSet(setname);
}
::Image::ImageDecoder *ImageMgr::createDecoder(const Common::String &fileType, int width, int height, int bpp) {
if (fileType == "image/png")
return new ::Image::PNGDecoder();
if (fileType == "image/x-u4raw")
return new U4RawImageDecoder(width, height, bpp);
if (fileType == "image/x-u4rle")
return new U4RleImageDecoder(width, height, bpp);
if (fileType == "image/x-u4lzw")
return new U4LzwImageDecoder(width, height, bpp);
if (fileType == "image/fmtowns-tif")
return new FMTOWNSImageDecoder(width, height, bpp, 510);
return nullptr;
}
ImageSet::~ImageSet() {
for (const auto &i : _info) {
ImageInfo *imageInfo = i._value;
if (imageInfo->_name != "screen")
delete imageInfo;
}
}
ImageInfo::~ImageInfo() {
for (auto &i : _subImages)
delete i._value;
if (_image != nullptr)
delete _image;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,216 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_IMAGEMGR_H
#define ULTIMA4_GFX_IMAGEMGR_H
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/core/observer.h"
#include "ultima/shared/std/containers.h"
namespace Common {
class File;
}
namespace Image {
class ImageDecoder;
}
namespace Ultima {
namespace Ultima4 {
class ConfigElement;
class Debug;
class ImageSet;
class Settings;
/*
* The image manager is responsible for loading and keeping track of
* the various images.
*/
#define BKGD_SHAPES "tiles"
#define BKGD_CHARSET "charset"
#define BKGD_BORDERS "borders"
#define BKGD_INTRO "title"
#define BKGD_OPTIONS_TOP "options_top"
#define BKGD_OPTIONS_BTM "options_btm"
#define BKGD_TREE "tree"
#define BKGD_PORTAL "portal"
#define BKGD_OUTSIDE "outside"
#define BKGD_INSIDE "inside"
#define BKGD_WAGON "wagon"
#define BKGD_GYPSY "gypsy"
#define BKGD_ABACUS "abacus"
#define BKGD_HONCOM "honcom"
#define BKGD_VALJUS "valjus"
#define BKGD_SACHONOR "sachonor"
#define BKGD_SPIRHUM "spirhum"
#define BKGD_ANIMATE "beasties"
#define BKGD_KEY "key"
#define BKGD_HONESTY "honesty"
#define BKGD_COMPASSN "compassn"
#define BKGD_VALOR "valor"
#define BKGD_JUSTICE "justice"
#define BKGD_SACRIFIC "sacrific"
#define BKGD_HONOR "honor"
#define BKGD_SPIRIT "spirit"
#define BKGD_HUMILITY "humility"
#define BKGD_TRUTH "truth"
#define BKGD_LOVE "love"
#define BKGD_COURAGE "courage"
#define BKGD_STONCRCL "stoncrcl"
#define BKGD_RUNE_INF "rune0"
#define BKGD_SHRINE_HON "rune1"
#define BKGD_SHRINE_COM "rune2"
#define BKGD_SHRINE_VAL "rune3"
#define BKGD_SHRINE_JUS "rune4"
#define BKGD_SHRINE_SAC "rune5"
#define BKGD_SHRINE_HNR "rune6"
#define BKGD_SHRINE_SPI "rune7"
#define BKGD_SHRINE_HUM "rune8"
#define BKGD_GEMTILES "gemtiles"
enum ImageFixup {
FIXUP_NONE,
FIXUP_INTRO,
FIXUP_ABYSS,
FIXUP_ABACUS,
FIXUP_DUNGNS,
FIXUP_BLACKTRANSPARENCYHACK,
FIXUP_FMTOWNSSCREEN
};
/**
* Image meta info.
*/
class ImageInfo {
public:
~ImageInfo();
Common::String _name;
Common::String _filename;
int _width, _height, _depth;
int _prescale;
Common::String _filetype;
int _tiles; /**< used to scale the without bleeding colors between adjacent tiles */
bool _introOnly; /**< whether can be freed after the intro */
int _transparentIndex; /**< color index to consider transparent */
bool _xu4Graphic; /**< an original xu4 graphic not part of u4dos or the VGA upgrade */
ImageFixup _fixup; /**< a routine to do miscellaneous fixes to the image */
Image *_image; /**< the image we're describing */
Common::HashMap<Common::String, SubImage *> _subImages;
bool hasBlackBackground();
};
/**
* The image manager singleton that keeps track of all the images.
*/
class ImageMgr : Observer<Settings *> {
public:
static ImageMgr *getInstance();
static void destroy();
/**
* Load in a background image from a ".ega" file.
*/
ImageInfo *get(const Common::String &name, bool returnUnscaled = false);
/**
* Returns information for the given image set.
*/
SubImage *getSubImage(const Common::String &name);
/**
* Free up any background images used only in the animations.
*/
void freeIntroBackgrounds();
const Std::vector<Common::String> &getSetNames();
Common::File *getImageFile(ImageInfo *info);
bool imageExists(ImageInfo *info);
private:
ImageMgr();
~ImageMgr();
void init();
ImageSet *loadImageSetFromConf(const ConfigElement &conf);
ImageInfo *loadImageInfoFromConf(const ConfigElement &conf);
SubImage *loadSubImageFromConf(const ImageInfo *info, const ConfigElement &conf);
/**
* Returns information for the given image set.
*/
ImageSet *getSet(const Common::String &setname);
/**
* Returns image information for the current image set.
*/
ImageInfo *getInfo(const Common::String &name);
/**
* Returns information for the given image set.
*/
ImageInfo *getInfoFromSet(const Common::String &name, ImageSet *set);
Common::String guessFileType(const Common::String &filename);
void fixupIntro(Image *im, int prescale);
void fixupAbyssVision(Image *im, int prescale);
void fixupAbacus(Image *im, int prescale);
/**
* Swap blue and green for the dungeon walls when facing north or
* south.
*/
void fixupDungNS(Image *im, int prescale);
/**
* The FMTowns images have a different screen dimension. This moves them
* up to what xu4 is accustomed to. south.
*/
void fixupFMTowns(Image *im, int prescale);
/**
* Find the new base image set when settings have changed.
*/
void update(Settings *newSettings);
/**
* Create an image decoder for the specified file type.
*/
::Image::ImageDecoder *createDecoder(const Common::String &fileType, int width, int height, int bpp);
static ImageMgr *_instance;
Common::HashMap<Common::String, ImageSet *> _imageSets;
Std::vector<Common::String> _imageSetNames;
ImageSet *_baseSet;
ImageInfo _screenInfo;
uint *_abyssData;
};
#define imageMgr (ImageMgr::getInstance())
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,458 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "ultima/ultima4/gfx/image.h"
#include "ultima/ultima4/gfx/scale.h"
#include "ultima/ultima4/core/utils.h"
#include "common/system.h"
namespace Ultima {
namespace Ultima4 {
Image *scalePoint(Image *src, int scale, int n);
Image *scale2xBilinear(Image *src, int scale, int n);
Image *scale2xSaI(Image *src, int scale, int N);
Image *scaleScale2x(Image *src, int scale, int N);
Scaler scalerGet(const Common::String &filter) {
if (filter == "point")
return &scalePoint;
else if (filter == "2xBi")
return &scale2xBilinear;
else if (filter == "2xSaI")
return &scale2xSaI;
else if (filter == "Scale2x")
return &scaleScale2x;
else
return nullptr;
}
/**
* Returns true if the given scaler can scale by 3 (as well as by 2).
*/
int scaler3x(const Common::String &filter) {
return filter == "Scale2x";
}
/**
* A simple row and column duplicating scaler.
*/
Image *scalePoint(Image *src, int scale, int n) {
int x, y, i, j;
Image *dest;
dest = Image::create(src->width() * scale, src->height() * scale, src->format());
if (!dest)
return nullptr;
if (dest->isIndexed())
dest->setPaletteFromImage(src);
for (y = 0; y < src->height(); y++) {
for (x = 0; x < src->width(); x++) {
for (i = 0; i < scale; i++) {
for (j = 0; j < scale; j++) {
uint index;
src->getPixelIndex(x, y, index);
dest->putPixelIndex(x * scale + j, y * scale + i, index);
}
}
}
}
return dest;
}
/**
* A scaler that interpolates each intervening pixel from it's two
* neighbors.
*/
Image *scale2xBilinear(Image *src, int scale, int n) {
int i, x, y, xoff, yoff;
RGBA a, b, c, d;
Image *dest;
/* this scaler works only with images scaled by 2x */
assertMsg(scale == 2, "invalid scale: %d", scale);
Graphics::PixelFormat format = src->isIndexed() ? g_system->getScreenFormat() : src->format();
dest = Image::create(src->width() * scale, src->height() * scale, format);
if (!dest)
return nullptr;
/*
* Each pixel in the source image is translated into four in the
* destination. The destination pixels are dependant on the pixel
* itself, and the three surrounding pixels (A is the original
* pixel):
* A B
* C D
* The four destination pixels mapping to A are calculated as
* follows:
* [ A ] [ (A+B)/2 ]
* [(A+C)/2] [(A+B+C+D)/4]
*/
for (i = 0; i < n; i++) {
for (y = (src->height() / n) * i; y < (src->height() / n) * (i + 1); y++) {
if (y == (src->height() / n) * (i + 1) - 1)
yoff = 0;
else
yoff = 1;
for (x = 0; x < src->width(); x++) {
if (x == src->width() - 1)
xoff = 0;
else
xoff = 1;
src->getPixel(x, y, a.r, a.g, a.b, a.a);
src->getPixel(x + xoff, y, b.r, b.g, b.b, b.a);
src->getPixel(x, y + yoff, c.r, c.g, c.b, c.a);
src->getPixel(x + xoff, y + yoff, d.r, d.g, d.b, d.a);
dest->putPixel(x * 2, y * 2, a.r, a.g, a.b, a.a);
dest->putPixel(x * 2 + 1, y * 2, (a.r + b.r) >> 1, (a.g + b.g) >> 1, (a.b + b.b) >> 1, (a.a + b.a) >> 1);
dest->putPixel(x * 2, y * 2 + 1, (a.r + c.r) >> 1, (a.g + c.g) >> 1, (a.b + c.b) >> 1, (a.a + c.a) >> 1);
dest->putPixel(x * 2 + 1, y * 2 + 1, (a.r + b.r + c.r + d.r) >> 2, (a.g + b.g + c.g + d.g) >> 2, (a.b + b.b + c.b + d.b) >> 2, (a.a + b.a + c.a + d.a) >> 2);
}
}
}
return dest;
}
int colorEqual(RGBA a, RGBA b) {
return
a.r == b.r &&
a.g == b.g &&
a.b == b.b &&
a.a == b.a;
}
RGBA colorAverage(RGBA a, RGBA b) {
RGBA result;
result.r = (a.r + b.r) >> 1;
result.g = (a.g + b.g) >> 1;
result.b = (a.b + b.b) >> 1;
result.a = (a.a + b.a) >> 1;
return result;
}
int _2xSaI_GetResult1(RGBA a, RGBA b, RGBA c, RGBA d) {
int x = 0;
int y = 0;
int r = 0;
if (colorEqual(a, c)) x++;
else if (colorEqual(b, c)) y++;
if (colorEqual(a, d)) x++;
else if (colorEqual(b, d)) y++;
if (x <= 1) r++;
if (y <= 1) r--;
return r;
}
int _2xSaI_GetResult2(RGBA a, RGBA b, RGBA c, RGBA d) {
int x = 0;
int y = 0;
int r = 0;
if (colorEqual(a, c)) x++;
else if (colorEqual(b, c)) y++;
if (colorEqual(a, d)) x++;
else if (colorEqual(b, d)) y++;
if (x <= 1) r--;
if (y <= 1) r++;
return r;
}
/**
* A more sophisticated scaler that interpolates each new pixel the
* surrounding pixels.
*/
Image *scale2xSaI(Image *src, int scale, int N) {
int ii, x, y, xoff0, xoff1, xoff2, yoff0, yoff1, yoff2;
RGBA a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p;
RGBA prod0, prod1, prod2;
Image *dest;
/* this scaler works only with images scaled by 2x */
assertMsg(scale == 2, "invalid scale: %d", scale);
Graphics::PixelFormat format = src->isIndexed() ? g_system->getScreenFormat() : src->format();
dest = Image::create(src->width() * scale, src->height() * scale, format);
if (!dest)
return nullptr;
/*
* Each pixel in the source image is translated into four in the
* destination. The destination pixels are dependant on the pixel
* itself, and the surrounding pixels as shown below (A is the
* original pixel):
* I E F J
* G A B K
* H C D L
* M N O P
*/
for (ii = 0; ii < N; ii++) {
for (y = (src->height() / N) * ii; y < (src->height() / N) * (ii + 1); y++) {
if (y == 0)
yoff0 = 0;
else
yoff0 = -1;
if (y == (src->height() / N) * (ii + 1) - 1) {
yoff1 = 0;
yoff2 = 0;
} else if (y == (src->height() / N) * (ii + 1) - 2) {
yoff1 = 1;
yoff2 = 1;
} else {
yoff1 = 1;
yoff2 = 2;
}
for (x = 0; x < src->width(); x++) {
if (x == 0)
xoff0 = 0;
else
xoff0 = -1;
if (x == src->width() - 1) {
xoff1 = 0;
xoff2 = 0;
} else if (x == src->width() - 2) {
xoff1 = 1;
xoff2 = 1;
} else {
xoff1 = 1;
xoff2 = 2;
}
src->getPixel(x, y, a.r, a.g, a.b, a.a);
src->getPixel(x + xoff1, y, b.r, b.g, b.b, b.a);
src->getPixel(x, y + yoff1, c.r, c.g, c.b, c.a);
src->getPixel(x + xoff1, y + yoff1, d.r, d.g, d.b, d.a);
src->getPixel(x, y + yoff0, e.r, e.g, e.b, e.a);
src->getPixel(x + xoff1, y + yoff0, f.r, f.g, f.b, f.a);
src->getPixel(x + xoff0, y, g.r, g.g, g.b, g.a);
src->getPixel(x + xoff0, y + yoff1, h.r, h.g, h.b, h.a);
src->getPixel(x + xoff0, y + yoff0, i.r, i.g, i.b, i.a);
src->getPixel(x + xoff2, y + yoff0, j.r, j.g, j.b, j.a);
src->getPixel(x + xoff0, y, k.r, k.g, k.b, k.a);
src->getPixel(x + xoff0, y + yoff1, l.r, l.g, l.b, l.a);
src->getPixel(x + xoff0, y + yoff2, m.r, m.g, m.b, m.a);
src->getPixel(x, y + yoff2, n.r, n.g, n.b, n.a);
src->getPixel(x + xoff1, y + yoff2, o.r, o.g, o.b, o.a);
src->getPixel(x + xoff2, y + yoff2, p.r, p.g, p.b, p.a);
if (colorEqual(a, d) && !colorEqual(b, c)) {
if ((colorEqual(a, e) && colorEqual(b, l)) ||
(colorEqual(a, c) && colorEqual(a, f) && !colorEqual(b, e) && colorEqual(b, j)))
prod0 = a;
else
prod0 = colorAverage(a, b);
if ((colorEqual(a, g) && colorEqual(c, o)) ||
(colorEqual(a, b) && colorEqual(a, h) && !colorEqual(g, c) && colorEqual(c, m)))
prod1 = a;
else
prod1 = colorAverage(a, c);
prod2 = a;
} else if (colorEqual(b, c) && !colorEqual(a, d)) {
if ((colorEqual(b, f) && colorEqual(a, h)) ||
(colorEqual(b, e) && colorEqual(b, d) && !colorEqual(a, f) && colorEqual(a, i)))
prod0 = b;
else
prod0 = colorAverage(a, b);
if ((colorEqual(c, h) && colorEqual(a, f)) ||
(colorEqual(c, g) && colorEqual(c, d) && !colorEqual(a, h) && colorEqual(a, i)))
prod1 = c;
else
prod1 = colorAverage(a, c);
prod2 = b;
} else if (colorEqual(a, d) && colorEqual(b, c)) {
if (colorEqual(a, b))
prod0 = prod1 = prod2 = a;
else {
int r = 0;
prod0 = colorAverage(a, b);
prod1 = colorAverage(a, c);
r += _2xSaI_GetResult1(a, b, g, e);
r += _2xSaI_GetResult2(b, a, k, f);
r += _2xSaI_GetResult2(b, a, h, n);
r += _2xSaI_GetResult1(a, b, l, o);
if (r > 0)
prod2 = a;
else if (r < 0)
prod2 = b;
else {
prod2.r = (a.r + b.r + c.r + d.r) >> 2;
prod2.g = (a.g + b.g + c.g + d.g) >> 2;
prod2.b = (a.b + b.b + c.b + d.b) >> 2;
}
}
} else {
if (colorEqual(a, c) && colorEqual(a, f) && !colorEqual(b, e) && colorEqual(b, j))
prod0 = a;
else if (colorEqual(b, e) && colorEqual(b, d) && !colorEqual(a, f) && colorEqual(a, i))
prod0 = b;
else
prod0 = colorAverage(a, b);
if (colorEqual(a, b) && colorEqual(a, h) && !colorEqual(g, c) && colorEqual(c, m))
prod1 = a;
else if (colorEqual(c, g) && colorEqual(c, d) && !colorEqual(a, h) && colorEqual(a, i))
prod1 = c;
else
prod1 = colorAverage(a, c);
prod2.r = (a.r + b.r + c.r + d.r) >> 2;
prod2.g = (a.g + b.g + c.g + d.g) >> 2;
prod2.b = (a.b + b.b + c.b + d.b) >> 2;
prod2.a = 255;
}
dest->putPixel((x << 1), (y << 1), a.r, a.g, a.b, a.a);
dest->putPixel((x << 1) + 1, (y << 1), prod0.r, prod0.g, prod0.b, prod0.a);
dest->putPixel((x << 1), (y << 1) + 1, prod1.r, prod1.g, prod1.b, prod1.a);
dest->putPixel((x << 1) + 1, (y << 1) + 1, prod2.r, prod2.g, prod2.b, prod2.a);
}
}
}
return dest;
}
/**
* A more sophisticated scaler that doesn't interpolate, but avoids
* the stair step effect by detecting angles.
*/
Image *scaleScale2x(Image *src, int scale, int n) {
int ii, x, y, xoff0, xoff1, yoff0, yoff1;
RGBA a, b, c, d, e, f, g, h, i;
RGBA e0, e1, e2, e3;
RGBA e4, e5, e6, e7;
Image *dest;
/* this scaler works only with images scaled by 2x or 3x */
assertMsg(scale == 2 || scale == 3, "invalid scale: %d", scale);
dest = Image::create(src->width() * scale, src->height() * scale, src->format());
if (!dest)
return nullptr;
if (dest->isIndexed())
dest->setPaletteFromImage(src);
/*
* Each pixel in the source image is translated into four (or
* nine) in the destination. The destination pixels are dependant
* on the pixel itself, and the eight surrounding pixels (E is the
* original pixel):
*
* A B C
* D E F
* G H I
*/
for (ii = 0; ii < n; ii++) {
for (y = (src->height() / n) * ii; y < (src->height() / n) * (ii + 1); y++) {
if (y == 0)
yoff0 = 0;
else
yoff0 = -1;
if (y == (src->height() / n) * (ii + 1) - 1)
yoff1 = 0;
else
yoff1 = 1;
for (x = 0; x < src->width(); x++) {
if (x == 0)
xoff0 = 0;
else
xoff0 = -1;
if (x == src->width() - 1)
xoff1 = 0;
else
xoff1 = 1;
src->getPixel(x + xoff0, y + yoff0, a.r, a.g, a.b, a.a);
src->getPixel(x, y + yoff0, b.r, b.g, b.b, b.a);
src->getPixel(x + xoff1, y + yoff0, c.r, c.g, c.b, c.a);
src->getPixel(x + xoff0, y, d.r, d.g, d.b, d.a);
src->getPixel(x, y, e.r, e.g, e.b, e.a);
src->getPixel(x + xoff1, y, f.r, f.g, f.b, f.a);
src->getPixel(x + xoff0, y + yoff1, g.r, g.g, g.b, g.a);
src->getPixel(x, y + yoff1, h.r, h.g, h.b, h.a);
src->getPixel(x + xoff1, y + yoff1, i.r, i.g, i.b, i.a);
// lissen diagonals (45°,135°,225°,315°)
// corner : if there is gradient towards a diagonal direction,
// take the color of surrounding points in this direction
e0 = colorEqual(d, b) && (!colorEqual(b, f)) && (!colorEqual(d, h)) ? d : e;
e1 = colorEqual(b, f) && (!colorEqual(b, d)) && (!colorEqual(f, h)) ? f : e;
e2 = colorEqual(d, h) && (!colorEqual(d, b)) && (!colorEqual(h, f)) ? d : e;
e3 = colorEqual(h, f) && (!colorEqual(d, h)) && (!colorEqual(b, f)) ? f : e;
// lissen eight more directions (22° or 67°, 112° or 157°...)
// middle of side : if there is a gradient towards one of these directions (middle of side direction and of direction of either diagonal around this side),
// take the color of surrounding points in this direction
e4 = colorEqual(e0, c) ? e0 : colorEqual(e1, a) ? e1 : e;
e5 = colorEqual(e2, a) ? e2 : colorEqual(e0, g) ? e0 : e;
e6 = colorEqual(e1, i) ? e1 : colorEqual(e3, c) ? e3 : e;
e7 = colorEqual(e3, g) ? e3 : colorEqual(e2, i) ? e2 : e;
if (scale == 2) {
dest->putPixel(x * 2, y * 2, e0.r, e0.g, e0.b, e0.a);
dest->putPixel(x * 2 + 1, y * 2, e1.r, e1.g, e1.b, e1.a);
dest->putPixel(x * 2, y * 2 + 1, e2.r, e2.g, e2.b, e2.a);
dest->putPixel(x * 2 + 1, y * 2 + 1, e3.r, e3.g, e3.b, e3.a);
} else if (scale == 3) {
dest->putPixel(x * 3, y * 3, e0.r, e0.g, e0.b, e0.a);
dest->putPixel(x * 3 + 1, y * 3, e4.r, e4.g, e4.b, e4.a);
dest->putPixel(x * 3 + 2, y * 3, e1.r, e1.g, e1.b, e1.a);
dest->putPixel(x * 3, y * 3 + 1, e5.r, e5.g, e5.b, e5.a);
dest->putPixel(x * 3 + 1, y * 3 + 1, e.r, e.g, e.b, e.a);
dest->putPixel(x * 3 + 2, y * 3 + 1, e6.r, e6.g, e6.b, e6.a);
dest->putPixel(x * 3, y * 3 + 2, e2.r, e2.g, e2.b, e2.a);
dest->putPixel(x * 3 + 1, y * 3 + 2, e7.r, e7.g, e7.b, e7.a);
dest->putPixel(x * 3 + 2, y * 3 + 2, e3.r, e3.g, e3.b, e3.a);
}
}
}
}
return dest;
}
} // End of namespace Ultima4
} // End of namespace Ultima

View File

@@ -0,0 +1,41 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_SCALE_H
#define ULTIMA4_GFX_SCALE_H
#include "ultima/ultima4/core/settings.h"
#include "common/str.h"
namespace Ultima {
namespace Ultima4 {
class Image;
typedef Image *(*Scaler)(Image *src, int scale, int n);
Scaler scalerGet(const Common::String &filter);
int scaler3x(const Common::String &filter);
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,308 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_SCREEN_H
#define ULTIMA4_GFX_SCREEN_H
#include "common/file.h"
#include "graphics/screen.h"
#include "ultima/ultima4/core/config.h"
#include "ultima/ultima4/core/types.h"
#include "ultima/ultima4/gfx/scale.h"
#include "ultima/ultima4/map/direction.h"
#include "ultima/ultima4/map/map_tile.h"
namespace Ultima {
namespace Ultima4 {
#define SCR_CYCLE_PER_SECOND 4
#define SCR_CYCLE_MAX 16
#define SCREEN_FRAME_TIME 50
#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 200
/*
* bitmasks for LOS shadows
*/
#define ____H 0x01 // obscured along the horizontal face
#define ___C_ 0x02 // obscured at the center
#define __V__ 0x04 // obscured along the vertical face
#define _N___ 0x80 // start of new raster
#define ___CH 0x03
#define __VCH 0x07
#define __VC_ 0x06
#define _N__H 0x81
#define _N_CH 0x83
#define _NVCH 0x87
#define _NVC_ 0x86
#define _NV__ 0x84
enum LayoutType {
LAYOUT_STANDARD,
LAYOUT_GEM,
LAYOUT_DUNGEONGEM
};
enum MouseCursor {
MC_DEFAULT,
MC_WEST,
MC_NORTH,
MC_EAST,
MC_SOUTH
};
class Image;
class Map;
class Tile;
class TileView;
class Coords;
class TileAnimSet;
class ImageInfo;
struct MouseCursorSurface : public Graphics::ManagedSurface {
Common::Point _hotspot;
};
struct MouseArea {
int _nPoints;
struct {
int x, y;
} _point[3];
MouseCursor _cursor;
Direction _direction;
};
struct Layout {
Common::String _name;
LayoutType _type;
Common::Point _tileShape;
Common::Rect _viewport;
};
class Screen : public Graphics::Screen {
private:
MouseCursorSurface *_mouseCursors[5];
int _currentMouseCursor;
Std::vector<TileAnimSet *> _tileAnimSets;
Layout *_gemLayout;
Common::HashMap<Common::String, int> _dungeonTileChars;
ImageInfo *_charSetInfo;
ImageInfo *_gemTilesInfo;
int _needPrompt;
Common::Point _cursorPos;
int _cursorStatus;
int _cursorEnabled;
int _los[VIEWPORT_W][VIEWPORT_H];
//bool _continueScreenRefresh;
uint32 _priorFrameTime;
public:
Std::vector<Common::String> _gemLayoutNames;
Std::vector<Common::String> _filterNames;
Std::vector<Common::String> _lineOfSightStyles;
int _currentCycle;
TileAnimSet *_tileAnims;
private:
/**
* Load the cursors
*/
void loadMouseCursors();
/**
* Loads the data for a single cursor from the passed file
*/
MouseCursorSurface *loadMouseCursor(Common::File &src);
void screenLoadGraphicsFromConf();
Layout *screenLoadLayoutFromConf(const ConfigElement &conf);
/**
* Draw a tile graphic on the screen.
*/
void screenShowGemTile(Layout *layout, Map *map, MapTile &t, bool focus, int x, int y);
/**
* Finds which tiles in the viewport are visible from the avatars
* location in the middle. (original DOS algorithm)
*/
void screenFindLineOfSight(Std::vector<MapTile> viewportTiles[VIEWPORT_W][VIEWPORT_H]);
/**
* Finds which tiles in the viewport are visible from the avatars
* location in the middle. (original DOS algorithm)
*/
void screenFindLineOfSightDOS(Std::vector<MapTile> viewportTiles[VIEWPORT_W][VIEWPORT_H]);
/**
* Finds which tiles in the viewport are visible from the avatars
* location in the middle.
*
* A new, more accurate LOS function
*
* Based somewhat off Andy McFadden's 1994 article,
* "Improvements to a Fast Algorithm for Calculating Shading
* and Visibility in a Two-Dimensional Field"
* -----
* https://fadden.com/tech/fast-los.html
*
* This function uses a lookup table to get the correct shadowmap,
* therefore, the table will need to be updated if the viewport
* dimensions increase. Also, the function assumes that the
* viewport width and height are odd values and that the player
* is always at the center of the screen.
*/
void screenFindLineOfSightEnhanced(Std::vector<MapTile> viewportTiles[VIEWPORT_W][VIEWPORT_H]);
/**
* Generates terms a and b for equation "ax + b = y" that defines the
* line containing the two given points. Vertical lines are special
* cased to return DBL_MAX for a and the x coordinate as b since they
* cannot be represented with the above formula.
*/
void screenGetLineTerms(int x1, int y1, int x2, int y2, double *a, double *b);
/**
* Determine if two points are on the same side of a line (or both on
* the line). The line is defined by the terms a and b of the
* equation "ax + b = y".
*/
int screenPointsOnSameSideOfLine(int x1, int y1, int x2, int y2, double a, double b);
int screenPointInTriangle(int x, int y, int tx1, int ty1, int tx2, int ty2, int tx3, int ty3);
Layout *screenGetGemLayout(const Map *map);
public:
Std::vector<Layout *> _layouts;
Scaler _filterScaler;
public:
Screen();
~Screen();
void init();
/**
* Reset the screen
*/
void clear();
/**
* Sets a given mouse cursor
*/
void setMouseCursor(MouseCursor cursor);
/**
* Re-initializes the screen and implements any changes made in settings
*/
void screenReInit();
void screenWait(int numberOfAnimationFrames);
/**
* Draw an image or subimage on the screen.
*/
void screenDrawImage(const Common::String &name, int x = 0, int y = 0);
void screenDrawImageInMapArea(const Common::String &bkgd);
void screenFrame();
void screenCycle();
void screenEraseMapArea();
void screenEraseTextArea(int x, int y, int width, int height);
void screenGemUpdate();
void screenMessage(const char *fmt, ...);
void screenPrompt();
void screenRedrawMapArea();
void screenRedrawTextArea(int x, int y, int width, int height);
/**
* Scroll the text in the message area up one position.
*/
void screenScrollMessageArea();
/**
* Do the tremor spell effect where the screen shakes.
*/
void screenShake(int iterations);
/**
* Draw a character from the charset onto the screen.
*/
void screenShowChar(int chr, int x, int y);
void screenTextAt(int x, int y, const char *fmt, ...);
/**
* Change the current text color
*/
void screenTextColor(int color);
bool screenTileUpdate(TileView *view, const Coords &coords, bool redraw = true); //Returns true if the screen was affected
/**
* Redraw the screen. If showmap is set, the normal map is drawn in
* the map area. If blackout is set, the map area is blacked out. If
* neither is set, the map area is left untouched.
*/
void screenUpdate(TileView *view, bool showmap, bool blackout);
void screenUpdateCursor();
void screenUpdateMoons();
void screenUpdateWind();
Std::vector<MapTile> screenViewportTile(uint width, uint height, int x, int y, bool &focus);
void screenShowCursor();
void screenHideCursor();
void screenEnableCursor();
void screenDisableCursor();
void screenSetCursorPos(int x, int y);
/**
* Determine if the given point is within a mouse area.
*/
int screenPointInMouseArea(int x, int y, const MouseArea *area);
/**
* Scale an image up. The resulting image will be scale * the
* original dimensions. The original image is no longer deleted.
* n is the number of tiles in the image; each tile is filtered
* separately. filter determines whether or not to filter the
* resulting image.
*/
Image *screenScale(Image *src, int scale, int n, int filter);
/**
* Scale an image down. The resulting image will be 1/scale * the
* original dimensions. The original image is no longer deleted.
*/
Image *screenScaleDown(Image *src, int scale);
};
extern Screen *g_screen;
extern const Std::vector<Common::String> &screenGetGemLayoutNames();
extern const Std::vector<Common::String> &screenGetFilterNames();
extern const Std::vector<Common::String> &screenGetLineOfSightStyles();
} // End of namespace Ultima4
} // End of namespace Ultima
#endif

View File

@@ -0,0 +1,58 @@
/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
#ifndef ULTIMA4_GFX_TEXTCOLOR_H
#define ULTIMA4_GFX_TEXTCOLOR_H
namespace Ultima {
namespace Ultima4 {
#define TEXT_BG_INDEX 0
#define TEXT_FG_PRIMARY_INDEX 15
#define TEXT_FG_SECONDARY_INDEX 7
#define TEXT_FG_SHADOW_INDEX 80
//
// text foreground colors
//
enum ColorFG {
FG_GREY = '\023',
FG_BLUE = '\024',
FG_PURPLE = '\025',
FG_GREEN = '\026',
FG_RED = '\027',
FG_YELLOW = '\030',
FG_WHITE = '\031'
};
//
// text background colors
//
enum ColorBG {
BG_NORMAL = '\032',
BG_BRIGHT = '\033'
};
} // End of namespace Ultima4
} // End of namespace Ultima
#endif