/* 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())); } }
/* 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); }
// ----------------------------------------------------------------------------- // 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()); } } }
/* 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; }
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; }
/* 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; }
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); }
// ----------------------------------------------------------------------------- // 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; }
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; }
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; }
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; }
/* 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; }
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; }
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; }
/* 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; }
// ----------------------------------------------------------------------------- // 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; }
/* 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; }
// ----------------------------------------------------------------------------- // 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(); }
/* 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; }
/* 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(); }
/* 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; }
/* 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); }
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; }
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; }
/* 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; }
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; }