/* 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); }
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; }
// ----------------------------------------------------------------------------- // Returns the sprite matching [name], loading it from resources if necessary. // Sprite name also supports wildcards (?) // ----------------------------------------------------------------------------- const MapTextureManager::Texture& MapTextureManager::sprite( std::string_view name, std::string_view translation, std::string_view palette) { // Don't bother looking for nameless sprites if (name.empty()) return tex_invalid; // Get sprite matching name /*auto hashname = StrUtil::upper(name); if (!translation.empty()) hashname += StrUtil::lower(translation); if (!palette.empty()) hashname += StrUtil::upper(palette);*/ auto hashname = fmt::format("{}{}{}", name, translation, palette); StrUtil::upperIP(hashname); auto& mtex = sprites_[hashname]; // Get desired filter type auto filter = OpenGL::TexFilter::Linear; if (map_tex_filter == 0) filter = OpenGL::TexFilter::NearestLinearMin; else if (map_tex_filter == 1) filter = OpenGL::TexFilter::Linear; else if (map_tex_filter == 2) filter = OpenGL::TexFilter::Linear; else if (map_tex_filter == 3) filter = OpenGL::TexFilter::NearestMipmap; // If the texture is loaded if (mtex.gl_id) { // If the texture filter matches the desired one, return it auto& tex_info = OpenGL::Texture::info(mtex.gl_id); if (tex_info.filter == filter) return mtex; else { // Otherwise, reload the texture OpenGL::Texture::clear(mtex.gl_id); mtex.gl_id = 0; } } // Sprite not found, look for it bool found = false; bool mirror = false; SImage image; // Palette8bit* pal = getResourcePalette(); auto entry = App::resources().getPatchEntry(name, "sprites", archive_); if (!entry) entry = App::resources().getPatchEntry(name, "", archive_); if (!entry && name.length() == 8) { std::string newname{ name }; newname[4] = name[6]; newname[5] = name[7]; newname[6] = name[4]; newname[7] = name[5]; entry = App::resources().getPatchEntry(newname, "sprites", archive_); if (entry) mirror = true; } if (entry) { found = true; Misc::loadImageFromEntry(&image, entry); } else // Try composite textures then { auto ctex = App::resources().getTexture(name, archive_); if (ctex && ctex->toImage(image, archive_, palette_.get(), true)) found = true; } // We have a valid image either from an entry or a composite texture. if (found) { auto pal = palette_.get(); // Apply translation if (!translation.empty()) image.applyTranslation(translation, pal, true); // Apply palette override if (!palette.empty()) { auto newpal = App::resources().getPaletteEntry(palette, archive_); if (newpal && newpal->size() == 768) { pal = image.palette(); pal->loadMem(newpal->data()); } } // Apply mirroring if (mirror) image.mirror(false); // Turn into GL texture mtex.gl_id = OpenGL::Texture::createFromImage(image, pal, filter, false); return mtex; } else if (name.back() == '?') { name.remove_suffix(1); auto stex = &sprite(fmt::format("{}0", name), translation, palette); if (!stex->gl_id) stex = &sprite(fmt::format("{}1", name), translation, palette); if (stex->gl_id) return *stex; if (!stex->gl_id && name.length() == 5) { for (char chr = 'A'; chr <= ']'; ++chr) { stex = &sprite(fmt::format("{}0{}0", name, chr), translation, palette); if (stex->gl_id) return *stex; stex = &sprite(fmt::format("{}1{}1", name, chr), translation, palette); if (stex->gl_id) return *stex; } } } return tex_invalid; }