Example #1
0
/* CTexture::parseDefine
 * Parses a HIRESTEX define block
 *******************************************************************/
bool CTexture::parseDefine(Tokenizer& tz)
{
	this->type = "Define";
	this->extended = true;
	this->defined = true;
	name = tz.getToken().Upper();
	def_width = tz.getInteger();
	def_height = tz.getInteger();
	width = def_width;
	height = def_height;
	ArchiveEntry* entry = theResourceManager->getPatchEntry(name);
	if (entry)
	{
		SImage image;
		if (image.open(entry->getMCData()))
		{
			width = image.getWidth();
			height = image.getHeight();
			scale_x = (double)width / (double)def_width;
			scale_y = (double)height / (double)def_height;
		}
	}
	CTPatchEx* patch = new CTPatchEx(name);
	patches.push_back(patch);
	return true;
}
// -----------------------------------------------------------------------------
// Loads all editor images (thing icons, etc) from the program resource archive
// -----------------------------------------------------------------------------
void MapTextureManager::importEditorImages(MapTexHashMap& map, ArchiveTreeNode* dir, std::string_view path) const
{
	SImage image;

	// Go through entries
	for (unsigned a = 0; a < dir->numEntries(); a++)
	{
		auto entry = dir->entryAt(a);

		// Load entry to image
		if (image.open(entry->data()))
		{
			// Create texture in hashmap
			auto name = fmt::format("{}{}", path, entry->nameNoExt());
			Log::info(4, "Loading editor texture {}", name);
			auto& mtex = map[name];
			mtex.gl_id = OpenGL::Texture::createFromImage(image, nullptr, OpenGL::TexFilter::Mipmap);
		}
	}

	// Go through subdirs
	for (unsigned a = 0; a < dir->nChildren(); a++)
	{
		auto subdir = dynamic_cast<ArchiveTreeNode*>(dir->child(a));
		importEditorImages(map, subdir, fmt::format("{}{}/", path, subdir->name()));
	}
}
Example #3
0
/* GfxCanvas::generateBrushShadow
 * Creates a mask texture of the brush to preview its effect
 *******************************************************************/
void GfxCanvas::generateBrushShadow()
{
	if (brush == nullptr) return;

	// Generate image
	SImage img;
	img.create(image->getWidth(), image->getHeight(), SIType::RGBA);
	for (int i = -4; i < 5; ++i)
		for (int j = -4; j < 5; ++j)
			if (brush->getPixel(i, j))
			{
				rgba_t col = paint_colour;
				if (editing_mode == 3 && translation)
					col = translation->translate(image->getPixel(cursor_pos.x + i,
						cursor_pos.y + j, getPalette()), getPalette());
				// Not sure what's the best way to preview cutting out
				// Mimicking the checkerboard pattern perhaps?
				// Cyan will do for now
				else if (editing_mode == 2)
					col = COL_CYAN;
				img.setPixel(cursor_pos.x + i, cursor_pos.y + j, col);
			}

	// Load it as a GL texture
	tex_brush->loadImage(&img);
}
Example #4
0
// -----------------------------------------------------------------------------
// Creates a mask texture of the brush to preview its effect
// -----------------------------------------------------------------------------
void GfxCanvas::generateBrushShadow()
{
	if (brush_ == nullptr)
		return;

	// Generate image
	SImage img;
	img.create(image_.width(), image_.height(), SImage::Type::RGBA);
	for (int i = -4; i < 5; ++i)
		for (int j = -4; j < 5; ++j)
			if (brush_->pixel(i, j))
			{
				auto col = paint_colour_;
				if (editing_mode_ == EditMode::Translate && translation_)
					col = translation_->translate(
						image_.pixelAt(cursor_pos_.x + i, cursor_pos_.y + j, &palette_), &palette_);
				// Not sure what's the best way to preview cutting out
				// Mimicking the checkerboard pattern perhaps?
				// Cyan will do for now
				else if (editing_mode_ == EditMode::Erase)
					col = ColRGBA::CYAN;
				img.setPixel(cursor_pos_.x + i, cursor_pos_.y + j, col);
			}

	// Load it as a GL texture
	OpenGL::Texture::clear(tex_brush_);
	tex_brush_ = OpenGL::Texture::createFromImage(img);
}
	void updateEntry()
	{
		// Read file
		MemChunk data;
		data.importFile(filename);

		// Read image
		SImage image;
		image.open(data, 0, "png");
		image.convertPaletted(&palette);

		// Convert image to entry gfx format
		SIFormat* format = SIFormat::getFormat(gfx_format);
		if (format)
		{
			MemChunk conv_data;
			if (format->saveImage(image, conv_data, &palette))
			{
				// Update entry data
				entry->importMemChunk(conv_data);
				EntryOperations::setGfxOffsets(entry, offsets.x, offsets.y);
			}
			else
			{
				LOG_MESSAGE(1, "Unable to convert external png to %s", format->getName());
			}
		}
	}
Example #6
0
/* GfxEntryPanel::statusString
 * Returns a string with extended editing/entry info for the status
 * bar
 *******************************************************************/
string GfxEntryPanel::statusString()
{
	// Setup status string
	SImage* image = getImage();
	string status = S_FMT("%dx%d", image->getWidth(), image->getHeight());

	// Colour format
	if (image->getType() == RGBA)
		status += ", 32bpp";
	else
		status += ", 8bpp";

	// PNG stuff
	if (entry->getType()->getFormat() == "img_png")
	{
		// alPh
		if (EntryOperations::getalPhChunk(entry))
			status += ", alPh";

		// tRNS
		if (EntryOperations::gettRNSChunk(entry))
			status += ", tRNS";
	}

	return status;
}
Example #7
0
	bool readImage(SImage& image, MemChunk& data, int index)
	{
		// Get image info
		SImage::info_t info;
		FIBITMAP*      bm = getFIInfo(data, info);

		// Check it created/read ok
		if (!bm)
		{
			Global::error = "Unable to read image data (unsupported format?)";
			return false;
		}

		// Get image palette if it exists
		RGBQUAD* bm_pal = FreeImage_GetPalette(bm);
		Palette  palette;
		if (bm_pal)
		{
			int a = 0;
			int b = FreeImage_GetColorsUsed(bm);
			if (b > 256)
				b = 256;
			for (; a < b; a++)
				palette.setColour(a, rgba_t(bm_pal[a].rgbRed, bm_pal[a].rgbGreen, bm_pal[a].rgbBlue, 255));
		}

		// Create image
		if (info.has_palette)
			image.create(info, &palette);
		else
			image.create(info);
		uint8_t* img_data = imageData(image);

		// Convert to 32bpp & flip vertically
		FIBITMAP* rgba = FreeImage_ConvertTo32Bits(bm);
		if (!rgba)
		{
			LOG_MESSAGE(1, "FreeImage_ConvertTo32Bits failed for image data");
			Global::error = "Error reading PNG data";
			return false;
		}
		FreeImage_FlipVertical(rgba);

		// Load raw RGBA data
		uint8_t* bits_rgba = FreeImage_GetBits(rgba);
		int      c         = 0;
		for (int a = 0; a < info.width * info.height; a++)
		{
			img_data[c++] = bits_rgba[a * 4 + 2]; // Red
			img_data[c++] = bits_rgba[a * 4 + 1]; // Green
			img_data[c++] = bits_rgba[a * 4];     // Blue
			img_data[c++] = bits_rgba[a * 4 + 3]; // Alpha
		}

		// Free memory
		FreeImage_Unload(rgba);
		FreeImage_Unload(bm);

		return true;
	}
Example #8
0
/* TextureXPanel::newTextureFromPatch
 * Creates a new texture called [name] from [patch]. The new texture
 * will be set to the dimensions of the patch, with the patch added
 * at 0,0
 *******************************************************************/
CTexture* TextureXPanel::newTextureFromPatch(string name, string patch)
{
	// Create new texture
	CTexture* tex = new CTexture();
	tex->setName(name);
	tex->setState(2);

	// Setup texture scale
	if (texturex.getFormat() == TXF_TEXTURES)
	{
		tex->setScale(1, 1);
		tex->setExtended(true);
	}
	else
		tex->setScale(0, 0);

	// Add patch
	tex->addPatch(patch, 0, 0);

	// Load patch image (to determine dimensions)
	SImage image;
	tex->loadPatchImage(0, image);

	// Set dimensions
	tex->setWidth(image.getWidth());
	tex->setHeight(image.getHeight());

	// Update variables
	modified = true;

	// Return the new texture
	return tex;
}
Example #9
0
void importEditorImages(MapTexHashMap& map, ArchiveTreeNode* dir, string path)
{
	SImage image;

	// Go through entries
	for (unsigned a = 0; a < dir->numEntries(); a++)
	{
		ArchiveEntry* entry = dir->getEntry(a);

		// Load entry to image
		if (image.open(entry->getMCData()))
		{
			// Create texture in hashmap
			string name = path + entry->getName(true);
			//wxLogMessage("Loading editor texture %s", CHR(name));
			map_tex_t& mtex = map[name];
			mtex.texture = new GLTexture(false);
			mtex.texture->setFilter(GLTexture::MIPMAP);
			mtex.texture->loadImage(&image);
		}
	}

	// Go through subdirs
	for (unsigned a = 0; a < dir->nChildren(); a++)
	{
		ArchiveTreeNode* subdir = (ArchiveTreeNode*)dir->getChild(a);
		importEditorImages(map, subdir, path + subdir->getName() + "/");
	}
}
void callBackDisplay(void *arg, SImage img)
{
  cvSaveImage("output.jpg", img->get());
  cvDestroyAllWindows();
  cvNamedWindow("IMAGE");
  cvShowImage("IMAGE", img->get());
  cvWaitKey(500);
}
Example #11
0
// -----------------------------------------------------------------------------
// Search for errors in texture list, return true if any are found
// -----------------------------------------------------------------------------
bool TextureXList::findErrors()
{
	bool ret = false;

	// Texture errors:
	// 1. A texture without any patch
	// 2. A texture with missing patches
	// 3. A texture with columns not covered by a patch

	for (unsigned a = 0; a < textures_.size(); a++)
	{
		if (textures_[a]->nPatches() == 0)
		{
			ret = true;
			Log::warning("Texture {}: {} does not have any patch", a, textures_[a]->name());
		}
		else
		{
			vector<uint8_t> columns(textures_[a]->width());
			memset(columns.data(), 0, textures_[a]->width());
			for (size_t i = 0; i < textures_[a]->nPatches(); ++i)
			{
				auto patch = textures_[a]->patches_[i]->patchEntry();
				if (patch == nullptr)
				{
					ret = true;
					Log::warning(
						"Texture {}: {}: patch {} cannot be found in any open archive",
						a,
						textures_[a]->name(),
						textures_[a]->patches_[i]->name());
					// Don't list missing columns when we don't know the size of the patch
					memset(columns.data(), 1, textures_[a]->width());
				}
				else
				{
					SImage img;
					img.open(patch->data());
					size_t start = std::max<size_t>(0, textures_[a]->patches_[i]->xOffset());
					size_t end   = std::min<size_t>(textures_[a]->width(), img.width() + start);
					for (size_t c = start; c < end; ++c)
						columns[c] = 1;
				}
			}
			for (size_t c = 0; c < textures_[a]->width(); ++c)
			{
				if (columns[c] == 0)
				{
					ret = true;
					Log::warning("Texture {}: {}: column {} without a patch", a, textures_[a]->name(), c);
					break;
				}
			}
		}
	}
	return ret;
}
Example #12
0
	int canWrite(SImage& image)
	{
		// If it's the correct size and colour format, it's writable
		if (image.getType() == PALMASK &&
		        validSize(image.getWidth(), image.getHeight()))
			return WRITABLE;

		// Otherwise, it can be converted via palettising and cropping
		return CONVERTIBLE;
	}
Example #13
0
	bool readImage(SImage& image, MemChunk& data, int index)
	{
		// Get info
		SImage::info_t info = getInfo(data, index);

		// Create image from data
		image.create(info.width, info.height, PALMASK);
		data.read(imageData(image), info.width * info.height, 0);
		image.fillAlpha(255);

		return true;
	}
Example #14
0
	bool writeImage(SImage& image, MemChunk& data, Palette* pal, int index)
	{
		// Can't write if RGBA
		if (image.getType() == RGBA)
			return false;

		// Check size
		if (!validSize(image.getWidth(), image.getHeight()))
			return false;

		// Just dump image data to memchunk
		data.clear();
		data.write(imageData(image), image.getWidth() * image.getHeight());

		return true;
	}
Example #15
0
/* EntryOperations::gfxConvert
 * Converts the image [entry] to [target_format], using conversion
 * options specified in [opt] and converting to [target_colformat]
 * colour format if possible. Returns false if the conversion failed,
 * true otherwise
 *******************************************************************/
bool EntryOperations::gfxConvert(ArchiveEntry* entry, string target_format, SIFormat::convert_options_t opt, int target_colformat)
{
	// Init variables
	SImage image;

	// Get target image format
	SIFormat* fmt = SIFormat::getFormat(target_format);
	if (fmt == SIFormat::unknownFormat())
		return false;

	// Check format and target colour type are compatible
	if (target_colformat >= 0 && !fmt->canWriteType((SIType)target_colformat))
	{
		if (target_colformat == RGBA)
			wxLogMessage("Format \"%s\" cannot be written as RGBA data", fmt->getName());
		else if (target_colformat == PALMASK)
			wxLogMessage("Format \"%s\" cannot be written as paletted data", fmt->getName());

		return false;
	}

	// Load entry to image
	Misc::loadImageFromEntry(&image, entry);

	// Check if we can write the image to the target format
	int writable = fmt->canWrite(image);
	if (writable == SIFormat::NOTWRITABLE)
	{
		wxLogMessage("Entry \"%s\" could not be converted to target format \"%s\"", entry->getName(), fmt->getName());
		return false;
	}
	else if (writable == SIFormat::CONVERTIBLE)
		fmt->convertWritable(image, opt);

	// Now we apply the target colour format (if any)
	if (target_colformat == PALMASK)
		image.convertPaletted(opt.pal_target, opt.pal_current);
	else if (target_colformat == RGBA)
		image.convertRGBA(opt.pal_current);

	// Finally, write new image data back to the entry
	fmt->saveImage(image, entry->getMCData(), opt.pal_target);

	return true;
}
Example #16
0
	bool exportEntry()
	{
		wxFileName fn(appPath(entry->getName(), DIR_TEMP));

		fn.SetExt("png");

		// Create image from entry
		SImage image;
		if (!Misc::loadImageFromEntry(&image, entry))
		{
			Global::error = "Could not read graphic";
			return false;
		}

		// Set export info
		gfx_format = image.getFormat()->getId();
		offsets = image.offset();
		palette.copyPalette(theMainWindow->getPaletteChooser()->getSelectedPalette(entry));

		// Write png data
		MemChunk png;
		SIFormat* fmt_png = SIFormat::getFormat("png");
		if (!fmt_png->saveImage(image, png, &palette))
		{
			Global::error = "Error converting to png";
			return false;
		}

		// Export file and start monitoring if successful
		filename = fn.GetFullPath();
		if (png.exportFile(filename))
		{
			file_modified = wxFileModificationTime(filename);
			Start(1000);
			return true;
		}

		return false;
	}
Example #17
0
	int canWrite(SImage& image)
	{
		// If it's the correct size and colour format, it's writable
		int width  = image.getWidth();
		int height = image.getHeight();

		// Shouldn't happen but...
		if (width < 0 || height < 0)
			return NOTWRITABLE;

		if (image.getType() == PALMASK && validSize(image.getWidth(), image.getHeight()))
			return WRITABLE;

		// Otherwise, check if it can be cropped to a valid size
		for (unsigned a = 0; a < n_valid_flat_sizes; a++)
			if (((unsigned)width >= valid_flat_size[a][0] && (unsigned)height >= valid_flat_size[a][1]
				 && valid_flat_size[a][2] == 1)
				|| gfx_extraconv)
				return CONVERTIBLE;

		return NOTWRITABLE;
	}
Example #18
0
/* MapTextureManager::getVerticalOffset
 * Detects offset hacks such as that used by the wall torch thing in
 * Heretic (type 50). If the Y offset is noticeably larger than the
 * sprite height, that means the thing is supposed to be rendered
 * above its real position.
 *******************************************************************/
int MapTextureManager::getVerticalOffset(string name)
{
	// Don't bother looking for nameless sprites
	if (name.IsEmpty())
		return 0;

	// Get sprite matching name
	ArchiveEntry* entry = theResourceManager->getPatchEntry(name, "sprites", archive);
	if (!entry) entry = theResourceManager->getPatchEntry(name, "", archive);
	if (entry)
	{
		SImage image;
		Misc::loadImageFromEntry(&image, entry);
		int h = image.getHeight();
		int o = image.offset().y;
		if (o > h)
			return o - h;
		else
			return 0;
	}

	return 0;
}
Example #19
0
// -----------------------------------------------------------------------------
// Detects offset hacks such as that used by the wall torch thing in Heretic.
// If the Y offset is noticeably larger than the sprite height, that means the
// thing is supposed to be rendered above its real position.
// -----------------------------------------------------------------------------
int MapTextureManager::verticalOffset(std::string_view name) const
{
	// Don't bother looking for nameless sprites
	if (name.empty())
		return 0;

	// Get sprite matching name
	auto entry = App::resources().getPatchEntry(name, "sprites", archive_);
	if (!entry)
		entry = App::resources().getPatchEntry(name, "", archive_);
	if (entry)
	{
		SImage image;
		Misc::loadImageFromEntry(&image, entry);
		int h = image.height();
		int o = image.offset().y;
		if (o > h)
			return o - h;
		else
			return 0;
	}

	return 0;
}
Example #20
0
/* CTexture::toImage
 * Generates a SImage representation of this texture, using patches
 * from [parent] primarily, and the palette [pal]
 *******************************************************************/
bool CTexture::toImage(SImage& image, Archive* parent, Palette8bit* pal, bool force_rgba)
{
	// Init image
	image.clear();
	image.resize(width, height);

	// Add patches
	SImage p_img(PALMASK);
	si_drawprops_t dp;
	dp.src_alpha = false;
	if (defined)
	{
		CTPatchEx* patch = (CTPatchEx*)patches[0];
		if (!loadPatchImage(0, p_img, parent, pal))
			return false;
		width = p_img.getWidth();
		height = p_img.getHeight();
		image.resize(width, height);
		scale_x = (double)width / (double)def_width;
		scale_y = (double)height / (double)def_height;
		image.drawImage(p_img, 0, 0, dp, pal, pal);
	}
	else if (extended)
	{
		// Extended texture

		// Add each patch to image
		for (unsigned a = 0; a < patches.size(); a++)
		{
			CTPatchEx* patch = (CTPatchEx*)patches[a];

			// Load patch entry
			if (!loadPatchImage(a, p_img, parent, pal))
				continue;

			// Handle offsets
			int ofs_x = patch->xOffset();
			int ofs_y = patch->yOffset();
			if (patch->useOffsets())
			{
				ofs_x -= p_img.offset().x;
				ofs_y -= p_img.offset().y;
			}

			// Apply translation before anything in case we're forcing rgba (can't translate rgba images)
			if (patch->getBlendType() == 1)
				p_img.applyTranslation(&(patch->getTranslation()), pal);

			// Convert to RGBA if forced
			if (force_rgba)
				p_img.convertRGBA(pal);

			// Flip/rotate if needed
			if (patch->flipX())
				p_img.mirror(false);
			if (patch->flipY())
				p_img.mirror(true);
			if (patch->getRotation() != 0)
				p_img.rotate(patch->getRotation());

			// Setup transparency blending
			dp.blend = NORMAL;
			dp.alpha = 1.0f;
			dp.src_alpha = false;
			if (patch->getStyle() == "CopyAlpha" || patch->getStyle() == "Overlay")
				dp.src_alpha = true;
			else if (patch->getStyle() == "Translucent" || patch->getStyle() == "CopyNewAlpha")
				dp.alpha = patch->getAlpha();
			else if (patch->getStyle() == "Add")
			{
				dp.blend = ADD;
				dp.alpha = patch->getAlpha();
			}
			else if (patch->getStyle() == "Subtract")
			{
				dp.blend = SUBTRACT;
				dp.alpha = patch->getAlpha();
			}
			else if (patch->getStyle() == "ReverseSubtract")
			{
				dp.blend = REVERSE_SUBTRACT;
				dp.alpha = patch->getAlpha();
			}
			else if (patch->getStyle() == "Modulate")
			{
				dp.blend = MODULATE;
				dp.alpha = patch->getAlpha();
			}

			// Setup patch colour
			if (patch->getBlendType() == 2)
				p_img.colourise(patch->getColour(), pal);
			else if (patch->getBlendType() == 3)
				p_img.tint(patch->getColour(), patch->getColour().fa(), pal);


			// Add patch to texture image
			image.drawImage(p_img, ofs_x, ofs_y, dp, pal, pal);
		}
	}
	else
	{
		// Normal texture

		// Add each patch to image
		for (unsigned a = 0; a < patches.size(); a++)
		{
			CTPatch* patch = patches[a];
			if (Misc::loadImageFromEntry(&p_img, patch->getPatchEntry(parent)))
				image.drawImage(p_img, patch->xOffset(), patch->yOffset(), dp, pal, pal);
		}
	}

	return true;
}
Example #21
0
// -----------------------------------------------------------------------------
// Draws the map
// -----------------------------------------------------------------------------
void MapPreviewCanvas::draw()
{
	// Setup colours
	auto col_view_background   = ColourConfiguration::colour("map_view_background");
	auto col_view_line_1s      = ColourConfiguration::colour("map_view_line_1s");
	auto col_view_line_2s      = ColourConfiguration::colour("map_view_line_2s");
	auto col_view_line_special = ColourConfiguration::colour("map_view_line_special");
	auto col_view_line_macro   = ColourConfiguration::colour("map_view_line_macro");
	auto col_view_thing        = ColourConfiguration::colour("map_view_thing");

	// Setup the viewport
	glViewport(0, 0, GetSize().x, GetSize().y);

	// Setup the screen projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, GetSize().x, 0, GetSize().y, -1, 1);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Clear
	glClearColor(
		((double)col_view_background.r) / 255.f,
		((double)col_view_background.g) / 255.f,
		((double)col_view_background.b) / 255.f,
		((double)col_view_background.a) / 255.f);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Translate to inside of pixel (otherwise inaccuracies can occur on certain gl implementations)
	if (OpenGL::accuracyTweak())
		glTranslatef(0.375f, 0.375f, 0);

	// Zoom/offset to show full map
	showMap();

	// Translate to middle of canvas
	glTranslated(GetSize().x * 0.5, GetSize().y * 0.5, 0);

	// Zoom
	glScaled(zoom_, zoom_, 1);

	// Translate to offset
	glTranslated(-offset_.x, -offset_.y, 0);

	// Setup drawing
	glDisable(GL_TEXTURE_2D);
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glLineWidth(1.5f);
	glEnable(GL_LINE_SMOOTH);

	// Draw lines
	for (auto& line : lines_)
	{
		// Check ends
		if (line.v1 >= verts_.size() || line.v2 >= verts_.size())
			continue;

		// Get vertices
		auto v1 = verts_[line.v1];
		auto v2 = verts_[line.v2];

		// Set colour
		if (line.special)
			OpenGL::setColour(col_view_line_special);
		else if (line.macro)
			OpenGL::setColour(col_view_line_macro);
		else if (line.twosided)
			OpenGL::setColour(col_view_line_2s);
		else
			OpenGL::setColour(col_view_line_1s);

		// Draw line
		glBegin(GL_LINES);
		glVertex2d(v1.x, v1.y);
		glVertex2d(v2.x, v2.y);
		glEnd();
	}

	// Load thing texture if needed
	if (!tex_loaded_)
	{
		// Load thing texture
		SImage image;
		auto   entry = App::archiveManager().programResourceArchive()->entryAtPath("images/thing/normal_n.png");
		if (entry)
		{
			image.open(entry->data());
			tex_thing_ = OpenGL::Texture::createFromImage(image, nullptr, OpenGL::TexFilter::Mipmap);
		}
		else
			tex_thing_ = 0;

		tex_loaded_ = true;
	}

	// Draw things
	if (map_view_things)
	{
		OpenGL::setColour(col_view_thing);
		if (tex_thing_)
		{
			double radius = 20;
			glEnable(GL_TEXTURE_2D);
			OpenGL::Texture::bind(tex_thing_);
			for (auto& thing : things_)
			{
				glPushMatrix();
				glTranslated(thing.x, thing.y, 0);
				glBegin(GL_QUADS);
				glTexCoord2f(0.0f, 0.0f);
				glVertex2d(-radius, -radius);
				glTexCoord2f(0.0f, 1.0f);
				glVertex2d(-radius, radius);
				glTexCoord2f(1.0f, 1.0f);
				glVertex2d(radius, radius);
				glTexCoord2f(1.0f, 0.0f);
				glVertex2d(radius, -radius);
				glEnd();
				glPopMatrix();
			}
		}
		else
		{
			glEnable(GL_POINT_SMOOTH);
			glPointSize(8.0f);
			glBegin(GL_POINTS);
			for (auto& thing : things_)
				glVertex2d(thing.x, thing.y);
			glEnd();
		}
	}

	glLineWidth(1.0f);
	glDisable(GL_LINE_SMOOTH);

	// Swap buffers (ie show what was drawn)
	SwapBuffers();
}
Example #22
0
/* GfxEntryPanel::detectOffsetType
 * Detects the offset view type of the current entry
 *******************************************************************/
int GfxEntryPanel::detectOffsetType()
{
	if (!entry)
		return GFXVIEW_DEFAULT;

	if (!entry->getParent())
		return GFXVIEW_DEFAULT;

	// Check what section of the archive the entry is in
	string section = entry->getParent()->detectNamespace(entry);

	if (section == "sprites")
	{
		SImage* img = getImage();
		int left = -img->offset().x;
		int right = -img->offset().x + img->getWidth();
		int top = -img->offset().y;
		int bottom = -img->offset().y + img->getHeight();

		if (top >= 0 && bottom <= 216 && left >= 0 && right <= 336)
			return GFXVIEW_HUD;
		else
			return GFXVIEW_SPRITE;
	}

	// Check for png image
	if (entry->getType()->getFormat() == "img_png")
	{
		if (getImage()->offset().x == 0 &&
		        getImage()->offset().y == 0)
			return GFXVIEW_DEFAULT;
		else
		{
			SImage* img = getImage();
			int left = -img->offset().x;
			int right = -img->offset().x + img->getWidth();
			int top = -img->offset().y;
			int bottom = -img->offset().y + img->getHeight();

			if (top >= 0 && bottom <= 216 && left >= 0 && right <= 336)
				return GFXVIEW_HUD;
			else
				return GFXVIEW_SPRITE;
		}
	}

	return GFXVIEW_DEFAULT;
}
Example #23
0
/* MapPreviewCanvas::draw
 * Draws the map
 *******************************************************************/
void MapPreviewCanvas::draw()
{
	// Setup colours
	rgba_t col_view_background = ColourConfiguration::getColour("map_view_background");
	rgba_t col_view_line_1s = ColourConfiguration::getColour("map_view_line_1s");
	rgba_t col_view_line_2s = ColourConfiguration::getColour("map_view_line_2s");
	rgba_t col_view_line_special = ColourConfiguration::getColour("map_view_line_special");
	rgba_t col_view_line_macro = ColourConfiguration::getColour("map_view_line_macro");
	rgba_t col_view_thing = ColourConfiguration::getColour("map_view_thing");

	// Setup the viewport
	glViewport(0, 0, GetSize().x, GetSize().y);

	// Setup the screen projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, GetSize().x, 0, GetSize().y, -1, 1);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Clear
	glClearColor(((double)col_view_background.r)/255.f, ((double)col_view_background.g)/255.f,
	             ((double)col_view_background.b)/255.f, ((double)col_view_background.a)/255.f);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	// Translate to inside of pixel (otherwise inaccuracies can occur on certain gl implementations)
	if (OpenGL::accuracyTweak())
		glTranslatef(0.375f, 0.375f, 0);

	// Zoom/offset to show full map
	showMap();

	// Translate to middle of canvas
	glTranslated(GetSize().x * 0.5, GetSize().y * 0.5, 0);

	// Zoom
	glScaled(zoom, zoom, 1);

	// Translate to offset
	glTranslated(-offset_x, -offset_y, 0);

	// Setup drawing
	glDisable(GL_TEXTURE_2D);
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glLineWidth(1.5f);
	glEnable(GL_LINE_SMOOTH);

	// Draw lines
	for (unsigned a = 0; a < lines.size(); a++)
	{
		mep_line_t line = lines[a];

		// Check ends
		if (line.v1 >= verts.size() || line.v2 >= verts.size())
			continue;

		// Get vertices
		mep_vertex_t v1 = verts[lines[a].v1];
		mep_vertex_t v2 = verts[lines[a].v2];

		// Set colour
		if (line.special)
			OpenGL::setColour(col_view_line_special);
		else if (line.macro)
			OpenGL::setColour(col_view_line_macro);
		else if (line.twosided)
			OpenGL::setColour(col_view_line_2s);
		else
			OpenGL::setColour(col_view_line_1s);

		// Draw line
		glBegin(GL_LINES);
		glVertex2d(v1.x, v1.y);
		glVertex2d(v2.x, v2.y);
		glEnd();
	}

	// Load thing texture if needed
	if (!tex_loaded)
	{
		// Load thing texture
		SImage image;
		ArchiveEntry* entry = theArchiveManager->programResourceArchive()->entryAtPath("images/thing/normal_n.png");
		if (entry)
		{
			image.open(entry->getMCData());
			tex_thing = new GLTexture(false);
			tex_thing->setFilter(GLTexture::MIPMAP);
			tex_thing->loadImage(&image);
		}
		else
			tex_thing = NULL;

		tex_loaded = true;
	}

	// Draw things
	if (map_view_things)
	{
		OpenGL::setColour(col_view_thing);
		if (tex_thing)
		{
			double radius = 20;
			glEnable(GL_TEXTURE_2D);
			tex_thing->bind();
			for (unsigned a = 0; a < things.size(); a++)
			{
				glPushMatrix();
				glTranslated(things[a].x, things[a].y, 0);
				glBegin(GL_QUADS);
				glTexCoord2f(0.0f, 0.0f);	glVertex2d(-radius, -radius);
				glTexCoord2f(0.0f, 1.0f);	glVertex2d(-radius, radius);
				glTexCoord2f(1.0f, 1.0f);	glVertex2d(radius, radius);
				glTexCoord2f(1.0f, 0.0f);	glVertex2d(radius, -radius);
				glEnd();
				glPopMatrix();
			}
		}
		else
		{
			glEnable(GL_POINT_SMOOTH);
			glPointSize(8.0f);
			glBegin(GL_POINTS);
			for (unsigned a = 0; a < things.size(); a++)
				glVertex2d(things[a].x, things[a].y);
			glEnd();
		}
	}

	glLineWidth(1.0f);
	glDisable(GL_LINE_SMOOTH);

	// Swap buffers (ie show what was drawn)
	SwapBuffers();
}
Example #24
0
/* GfxEntryPanel::saveEntry
 * Saves any changes to the entry
 *******************************************************************/
bool GfxEntryPanel::saveEntry()
{
	// Write new image data if modified
	bool ok = true;
	if (image_data_modified)
	{
		SImage* image = getImage();
		SIFormat* format = image->getFormat();

		string error = "";
		ok = false;
		int writable = format->canWrite(*image);
		if (format == SIFormat::unknownFormat())
			error = "Image is of unknown format";
		else if (writable == SIFormat::NOTWRITABLE)
			error = S_FMT("Writing unsupported for format \"%s\"", format->getName());
		else
		{
			// Convert image if necessary (using default options)
			if (writable == SIFormat::CONVERTIBLE)
			{
				format->convertWritable(*image, SIFormat::convert_options_t());
				wxLogMessage("Image converted for writing");
			}

			if (format->saveImage(*image, entry->getMCData(), gfx_canvas->getPalette()))
				ok = true;
			else
				error = "Error writing image";
		}

		if (ok)
		{
			// Set modified
			entry->setState(1);

			// Re-detect type
			EntryType* oldtype = entry->getType();
			EntryType::detectEntryType(entry);

			// Update extension if type changed
			if (oldtype != entry->getType())
				entry->setExtensionByType();
		}
		else
			wxMessageBox(wxString("Cannot save changes to image: ") + error, "Error", wxICON_ERROR);
	}
	// Otherwise just set offsets
	else
		EntryOperations::setGfxOffsets(entry, spin_xoffset->GetValue(), spin_yoffset->GetValue());

	// Apply alPh/tRNS options
	if (entry->getType()->getFormat() == "img_png")
	{
		bool alph = EntryOperations::getalPhChunk(entry);
		bool trns = EntryOperations::gettRNSChunk(entry);

		if (alph != menu_custom->IsChecked(theApp->getAction("pgfx_alph")->getWxId()))
			EntryOperations::modifyalPhChunk(entry, !alph);
		if (trns != menu_custom->IsChecked(theApp->getAction("pgfx_trns")->getWxId()))
			EntryOperations::modifytRNSChunk(entry, !trns);
	}

	if (ok)
		setModified(false);

	return ok;
}
Example #25
0
/* MapPreviewCanvas::createImage
 * Draws the map in an image
 * TODO: Factorize code with normal draw() and showMap() functions.
 * TODO: Find a way to generate an arbitrary-sized image through
 * tiled rendering.
 *******************************************************************/
void MapPreviewCanvas::createImage(ArchiveEntry& ae, int width, int height)
{
	// Find extents of map
	mep_vertex_t m_min(999999.0, 999999.0);
	mep_vertex_t m_max(-999999.0, -999999.0);
	for (unsigned a = 0; a < verts.size(); a++)
	{
		if (verts[a].x < m_min.x)
			m_min.x = verts[a].x;
		if (verts[a].x > m_max.x)
			m_max.x = verts[a].x;
		if (verts[a].y < m_min.y)
			m_min.y = verts[a].y;
		if (verts[a].y > m_max.y)
			m_max.y = verts[a].y;
	}
	double mapwidth = m_max.x - m_min.x;
	double mapheight = m_max.y - m_min.y;

	if (width == 0) width = -5;
	if (height == 0) height = -5;
	if (width < 0)
		width = mapwidth / abs(width);
	if (height < 0)
		height = mapheight / abs(height);

	// Setup colours
	rgba_t col_save_background = ColourConfiguration::getColour("map_image_background");
	rgba_t col_save_line_1s = ColourConfiguration::getColour("map_image_line_1s");
	rgba_t col_save_line_2s = ColourConfiguration::getColour("map_image_line_2s");
	rgba_t col_save_line_special = ColourConfiguration::getColour("map_image_line_special");
	rgba_t col_save_line_macro = ColourConfiguration::getColour("map_image_line_macro");

	// Setup OpenGL rigmarole
	GLuint texID, fboID;
	if (GLEW_ARB_framebuffer_object)
	{
		glGenTextures(1, &texID);
		glBindTexture(GL_TEXTURE_2D, texID);
		// We don't use mipmaps, but OpenGL will refuse to attach
		// the texture to the framebuffer if they are not present
		glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
		glBindTexture(GL_TEXTURE_2D, 0);
		glGenFramebuffersEXT(1, &fboID);
		glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fboID);
		glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_2D, texID, 0);
		GLenum status = glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
	}

	glViewport(0, 0, width, height);

	// Setup the screen projection
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glOrtho(0, width, 0, height, -1, 1);

	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();

	// Clear
	glClearColor(((double)col_save_background.r)/255.f, ((double)col_save_background.g)/255.f,
	             ((double)col_save_background.b)/255.f, ((double)col_save_background.a)/255.f);
	glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT);

	// Translate to inside of pixel (otherwise inaccuracies can occur on certain gl implementations)
	if (OpenGL::accuracyTweak())
		glTranslatef(0.375f, 0.375f, 0);

	// Zoom/offset to show full map
	// Offset to center of map
	offset_x = m_min.x + (mapwidth * 0.5);
	offset_y = m_min.y + (mapheight * 0.5);

	// Zoom to fit whole map
	double x_scale = ((double)width) / mapwidth;
	double y_scale = ((double)height) / mapheight;
	zoom = MIN(x_scale, y_scale);
	zoom *= 0.95;

	// Translate to middle of canvas
	glTranslated(width>>1, height>>1, 0);

	// Zoom
	glScaled(zoom, zoom, 1);

	// Translate to offset
	glTranslated(-offset_x, -offset_y, 0);

	// Setup drawing
	glDisable(GL_TEXTURE_2D);
	glColor4f(1.0f, 1.0f, 1.0f, 1.0f);
	glLineWidth(map_image_thickness);
	glEnable(GL_LINE_SMOOTH);

	// Draw lines
	for (unsigned a = 0; a < lines.size(); a++)
	{
		mep_line_t line = lines[a];

		// Check ends
		if (line.v1 >= verts.size() || line.v2 >= verts.size())
			continue;

		// Get vertices
		mep_vertex_t v1 = verts[lines[a].v1];
		mep_vertex_t v2 = verts[lines[a].v2];

		// Set colour
		if (line.special)
			OpenGL::setColour(col_save_line_special);
		else if (line.macro)
			OpenGL::setColour(col_save_line_macro);
		else if (line.twosided)
			OpenGL::setColour(col_save_line_2s);
		else
			OpenGL::setColour(col_save_line_1s);

		// Draw line
		glBegin(GL_LINES);
		glVertex2d(v1.x, v1.y);
		glVertex2d(v2.x, v2.y);
		glEnd();
	}

	glLineWidth(1.0f);
	glDisable(GL_LINE_SMOOTH);

	uint8_t* ImageBuffer = new uint8_t[width * height * 4];
	glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, ImageBuffer);

	if (GLEW_ARB_framebuffer_object)
	{
		glBindFramebuffer(GL_FRAMEBUFFER, 0);
		glDeleteTextures( 1, &texID );
		glDeleteFramebuffersEXT( 1, &fboID );
	}
	SImage img;
	img.setImageData(ImageBuffer, width, height, RGBA);
	img.mirror(true);
	MemChunk mc;
	SIFormat::getFormat("png")->saveImage(img, mc);
	ae.importMemChunk(mc);
}
Example #26
0
	bool convertWritable(SImage& image, ConvertOptions opt)
	{
		// Firstly, make image paletted
		image.convertPaletted(opt.pal_target, opt.pal_current);

		// Secondly, remove any alpha information
		image.fillAlpha(255);

		// Quick hack for COLORMAP size
		// TODO: Remove me when a proper COLORMAP editor is implemented
		if (image.getWidth() == 256 && image.getHeight() >= 32 && image.getHeight() <= 34)
			return true;

		// Check for fullscreen/autopage size
		if (image.getWidth() == 320)
			return true;

		// And finally, find a suitable flat size and crop to that size
		int width  = 0;
		int height = 0;

		for (unsigned a = 1; a < n_valid_flat_sizes; a++)
		{
			bool writable = (valid_flat_size[a][2] == 1 || gfx_extraconv);

			// Check for exact match (no need to crop)
			if (image.getWidth() == valid_flat_size[a][0] && image.getHeight() == valid_flat_size[a][1] && writable)
				return true;

			// If the flat will fit within this size, crop to the previous size
			// (this works because flat sizes list is in size-order)
			if (image.getWidth() <= (int)valid_flat_size[a][0] && image.getHeight() <= (int)valid_flat_size[a][1]
				&& width > 0 && height > 0)
			{
				image.crop(0, 0, width, height);
				return true;
			}

			// Save 'previous' valid size
			if (writable)
			{
				width  = valid_flat_size[a][0];
				height = valid_flat_size[a][1];
			}
		}

		return false;
	}
Example #27
0
bool PaletteEntryPanel::generateColormaps()
{
	if (!entry || !entry->getParent() || ! palettes[0])
		return false;

	MemChunk mc;
	SImage img;
	MemChunk imc;
	mc.reSize(34*256);
	mc.seek(0, SEEK_SET);
	imc.reSize(34*256*4);
	imc.seek(0, SEEK_SET);
	uint8_t rgba[4];
	rgba[3] = 255;

	rgba_t rgb;
	float grey;
	// Generate 34 maps: the first 32 for diminishing light levels,
	// the 33th for the inverted grey map used by invulnerability.
	// The 34th colormap remains empty and black.
	for (size_t l = 0; l < 34; ++l)
	{
		for (size_t c = 0; c < 256; ++c)
		{
			rgb = palettes[0]->colour(c);
			if (l < 32)
			{
				// Generate light maps
				DIMINISH(rgb.r, l);
				DIMINISH(rgb.g, l);
				DIMINISH(rgb.b, l);
#if (0)
			}
			else if (l == GREENMAP)
			{
				// Point of mostly useless trivia: the green "light amp" colormap in the Press Release beta
				// have colors that, on average, correspond to a bit less than (R*75/256, G*225/256, B*115/256)
#endif
			}
			else if (l == GRAYMAP)
			{
				// Generate inverse map
				grey = ((float)rgb.r/256.0 * col_greyscale_r) + ((float)rgb.g/256.0 * col_greyscale_g) + ((float)rgb.b/256.0 * col_greyscale_b);
				grey = 1.0 - grey;
				// Clamp value: with Id Software's values, the sum is greater than 1.0 (0.299+0.587+0.144=1.030)
				// This means the negation above can give a negative value (for example, with RGB values of 247 or more),
				// which will not be converted correctly to unsigned 8-bit int in the rgba_t struct.
				if (grey < 0.0) grey = 0;
				rgb.r = rgb.g = rgb.b = grey*255;
			}
			else
			{
				// Fill with 0
				rgb = palettes[0]->colour(0);
			}
			rgba[0] = rgb.r; rgba[1] = rgb.g; rgba[2] = rgb.b;
			imc.write(&rgba, 4);
			mc[(256*l)+c] = palettes[0]->nearestColour(rgb);
		}
	}
#if 0
	// Create truecolor image
	uint8_t* imd = new uint8_t[256*34*4];
	memcpy(imd, imc.getData(), 256*34*4);
	img.setImageData(imd, 256, 34, RGBA);
	// imd will be freed by img's destructor
	ArchiveEntry* tcolormap;
	string name = entry->getName(true) + "-tcm.png";
	tcolormap = new ArchiveEntry(name);
	if (tcolormap)
	{
		entry->getParent()->addEntry(tcolormap);
		SIFormat::getFormat("png")->saveImage(img, tcolormap->getMCData());
		EntryType::detectEntryType(tcolormap);
	}
#endif
	// Now override or create new entry
	ArchiveEntry* colormap;
	colormap = entry->getParent()->getEntry("COLORMAP", true);
	bool preexisting = colormap != NULL;
	if (!colormap)
	{
		// We need to create this entry
		colormap = new ArchiveEntry("COLORMAP.lmp", 34*256);
	}
	if (!colormap)
		return false;
	colormap->importMemChunk(mc);
	if (!preexisting)
	{
		entry->getParent()->addEntry(colormap);
	}
	return true;
}
Example #28
0
/* EntryOperations::createTexture
 * Same as addToPatchTable, but also creates a single-patch texture
 * from each added patch
 *******************************************************************/
bool EntryOperations::createTexture(vector<ArchiveEntry*> entries)
{
	// Check any entries were given
	if (entries.size() == 0)
		return true;

	// Get parent archive
	Archive* parent = entries[0]->getParent();

	// Create texture entries if needed
	if (!TextureXEditor::setupTextureEntries(parent))
		return false;

	// Find texturex entry to add to
	Archive::search_options_t opt;
	opt.match_type = EntryType::getType("texturex");
	ArchiveEntry* texturex = parent->findFirst(opt);

	// Check it exists
	bool zdtextures = false;
	if (!texturex)
	{
		opt.match_type = EntryType::getType("zdtextures");
		texturex = parent->findFirst(opt);

		if (!texturex)
			return false;
		else
			zdtextures = true;
	}

	// Find patch table in parent archive
	ArchiveEntry* pnames = NULL;
	if (!zdtextures)
	{
		opt.match_type = EntryType::getType("pnames");
		pnames = parent->findLast(opt);

		// Check it exists
		if (!pnames)
			return false;
	}

	// Check entries aren't locked (texture editor open or iwad)
	if ((pnames && pnames->isLocked()) || texturex->isLocked())
	{
		if (parent->isReadOnly())
			wxMessageBox("Cannot perform this action on an IWAD", "Error", wxICON_ERROR);
		else
			wxMessageBox("Cannot perform this action because one or more texture related entries is locked. Please close the archive's texture editor if it is open.", "Error", wxICON_ERROR);

		return false;
	}

	TextureXList tx;
	PatchTable ptable;
	if (zdtextures)
	{
		// Load TEXTURES
		tx.readTEXTURESData(texturex);
	}
	else
	{
		// Load patch table
		ptable.loadPNAMES(pnames);

		// Load TEXTUREx
		tx.readTEXTUREXData(texturex, ptable);
	}

	// Create textures from entries
	SImage image;
	for (unsigned a = 0; a < entries.size(); a++)
	{
		// Check entry type
		if (!(entries[a]->getType()->extraProps().propertyExists("image")))
		{
			wxLogMessage("Entry %s is not a valid image", entries[a]->getName());
			continue;
		}

		// Check entry name
		string name = entries[a]->getName(true);
		if (name.Length() > 8)
		{
			wxLogMessage("Entry %s has too long a name to add to the patch table (name must be 8 characters max)", entries[a]->getName());
			continue;
		}

		// Add to patch table
		if (!zdtextures)
			ptable.addPatch(name);

		// Load patch to temp image
		Misc::loadImageFromEntry(&image, entries[a]);

		// Create texture
		CTexture* ntex = new CTexture(zdtextures);
		ntex->setName(name);
		ntex->addPatch(name, 0, 0);
		ntex->setWidth(image.getWidth());
		ntex->setHeight(image.getHeight());

		// Setup texture scale
		if (tx.getFormat() == TXF_TEXTURES)
			ntex->setScale(1, 1);
		else
			ntex->setScale(0, 0);

		// Add to texture list
		tx.addTexture(ntex);
	}

	if (zdtextures)
	{
		// Write texture data back to textures entry
		tx.writeTEXTURESData(texturex);
	}
	else
	{
		// Write patch table data back to pnames entry
		ptable.writePNAMES(pnames);

		// Write texture data back to texturex entry
		tx.writeTEXTUREXData(texturex, ptable);
	}

	return true;
}
Example #29
0
GLTexture* MapTextureManager::getSprite(string name, string translation, string palette)
{
	// Don't bother looking for nameless sprites
	if (name.IsEmpty())
		return NULL;

	// Get sprite matching name
	string hashname = name.Upper();
	if (!translation.IsEmpty())
		hashname += translation.Lower();
	if (!palette.IsEmpty())
		hashname += palette.Upper();
	map_tex_t& mtex = sprites[hashname];

	// Get desired filter type
	int filter = 1;
	if (map_tex_filter == 0)
		filter = GLTexture::NEAREST_LINEAR_MIN;
	else if (map_tex_filter == 1)
		filter = GLTexture::LINEAR;
	else if (map_tex_filter == 2)
		filter = GLTexture::LINEAR;
	else if (map_tex_filter == 3)
		filter = GLTexture::NEAREST_MIPMAP;

	// If the texture is loaded
	if (mtex.texture)
	{
		// If the texture filter matches the desired one, return it
		if (mtex.texture->getFilter() == filter)
			return mtex.texture;
		else
		{
			// Otherwise, reload the texture
			delete mtex.texture;
			mtex.texture = NULL;
		}
	}

	// Sprite not found, look for it
	bool found = false;
	bool mirror = false;
	SImage image;
	Palette8bit* pal = getResourcePalette();
	ArchiveEntry* entry = theResourceManager->getPatchEntry(name, "sprites", archive);
	if (!entry) entry = theResourceManager->getPatchEntry(name, "", archive);
	if (!entry && name.length() == 8)
	{
		string newname = name;
		newname[4] = name[6]; newname[5] = name[7]; newname[6] = name[4]; newname[7] = name[5];
		entry = theResourceManager->getPatchEntry(newname, "sprites", archive);
		if (entry) mirror = true;
	}
	if (entry)
	{
		found = true;
		Misc::loadImageFromEntry(&image, entry);
	}
	else  	// Try composite textures then
	{
		CTexture* ctex = theResourceManager->getTexture(name, archive);
		if (ctex && ctex->toImage(image, archive, pal))
			found = true;
	}

	// We have a valid image either from an entry or a composite texture.
	if (found)
	{
		// Apply translation
		if (!translation.IsEmpty()) image.applyTranslation(translation, pal);
		// Apply palette override
		if (!palette.IsEmpty())
		{
			ArchiveEntry* newpal = theResourceManager->getPaletteEntry(palette, archive);
			if (newpal && newpal->getSize() == 768)
			{
				// Why is this needed?
				// Copying data in pal->loadMem shouldn't
				// change it in the original entry...
				// We shouldn't need to copy the data in a temporary place first.
				pal = image.getPalette();
				MemChunk mc;
				mc.importMem(newpal->getData(), newpal->getSize());
				pal->loadMem(mc);
			}
		}
		// Apply mirroring
		if (mirror) image.mirror(false);
		// Turn into GL texture
		mtex.texture = new GLTexture(false);
		mtex.texture->setFilter(filter);
		mtex.texture->setTiling(false);
		mtex.texture->loadImage(&image, pal);
		return mtex.texture;
	}
	else if (name.EndsWith("?"))
	{
		name.RemoveLast(1);
		GLTexture* sprite = getSprite(name + '0', translation, palette);
		if (!sprite)
			sprite = getSprite(name + '1', translation, palette);
		if (sprite)
			return sprite;
		if (!sprite && name.length() == 5)
		{
			for (char chr = 'A'; chr <= ']'; ++chr)
			{
				sprite = getSprite(name + '0' + chr + '0', translation, palette);
				if (sprite) return sprite;
				sprite = getSprite(name + '1' + chr + '1', translation, palette);
				if (sprite) return sprite;
			}
		}
	}

	return NULL;
}
/* MapTextureManager::getTexture
 * Returns the texture matching [name]. Loads it from resources if
 * necessary. If [mixed] is true, flats are also searched if no
 * matching texture is found
 *******************************************************************/
GLTexture* MapTextureManager::getTexture(string name, bool mixed)
{
	// Get texture matching name
	map_tex_t& mtex = textures[name.Upper()];

	// Get desired filter type
	int filter = 1;
	if (map_tex_filter == 0)
		filter = GLTexture::NEAREST_LINEAR_MIN;
	else if (map_tex_filter == 1)
		filter = GLTexture::LINEAR;
	else if (map_tex_filter == 2)
		filter = GLTexture::LINEAR_MIPMAP;
	else if (map_tex_filter == 3)
		filter = GLTexture::NEAREST_MIPMAP;

	// If the texture is loaded
	if (mtex.texture)
	{
		// If the texture filter matches the desired one, return it
		if (mtex.texture->getFilter() == filter)
			return mtex.texture;
		else
		{
			// Otherwise, reload the texture
			if (mtex.texture != &(GLTexture::missingTex())) delete mtex.texture;
			mtex.texture = NULL;
		}
	}

	// Texture not found or unloaded, look for it
	//Palette8bit* pal = getResourcePalette();

	// Look for stand-alone textures first
	ArchiveEntry* etex = theResourceManager->getTextureEntry(name, "hires", archive);
	int textypefound = TEXTYPE_HIRES;
	if (etex == NULL)
	{
		etex = theResourceManager->getTextureEntry(name, "textures", archive);
		textypefound = TEXTYPE_TEXTURE;
	}
	if (etex)
	{
		SImage image;
		// Get image format hint from type, if any
		if (Misc::loadImageFromEntry(&image, etex))
		{
			mtex.texture = new GLTexture(false);
			mtex.texture->setFilter(filter);
			mtex.texture->loadImage(&image, palette);

			// Handle hires texture scale
			if (textypefound == TEXTYPE_HIRES)
			{
				ArchiveEntry* ref = theResourceManager->getTextureEntry(name, "textures", archive);
				if (ref)
				{
					SImage imgref;
					if (Misc::loadImageFromEntry(&imgref, ref))
					{
						int w, h, sw, sh;
						w = image.getWidth();
						h = image.getHeight();
						sw = imgref.getWidth();
						sh = imgref.getHeight();
						mtex.texture->setScale((double)sw/(double)w, (double)sh/(double)h);
					}
				}
			}
		}
	}

	// Try composite textures then
	CTexture* ctex = theResourceManager->getTexture(name, archive);
	if (ctex && (!mtex.texture || textypefound == TEXTYPE_FLAT))
	{
		textypefound = TEXTYPE_WALLTEXTURE;
		SImage image;
		if (ctex->toImage(image, archive, palette))
		{
			mtex.texture = new GLTexture(false);
			mtex.texture->setFilter(filter);
			mtex.texture->loadImage(&image, palette);
			double sx = ctex->getScaleX(); if (sx == 0) sx = 1.0;
			double sy = ctex->getScaleY(); if (sy == 0) sy = 1.0;
			mtex.texture->setScale(1.0/sx, 1.0/sy);
		}
	}

	// Not found
	if (!mtex.texture)
	{
		// Try flats if mixed
		if (mixed)
			return getFlat(name, false);

		// Otherwise use missing texture
		else
			mtex.texture = &(GLTexture::missingTex());
	}

	return mtex.texture;
}