Jump to content

Sub Image Crop Tool & SubSlicer


Recommended Posts

  • Honorable Member

M2 Download Center

This is the hidden content, please
( Internal )

This is the hidden content, please

I did this 4 months ago out of necessity.

Maybe it will be useful for someone.

SubSlicer's author: sanii187

https://metin2.download/picture/w98yJ1o9Bc0QNq3Ow220aNxu68G6SX2Z/.gif

 

password: black

Edited by Mali
  • Metin2 Dev 108
  • Dislove 1
  • Angry 1
  • Think 2
  • Good 44
  • Love 5
  • Love 49

 

Link to comment
Share on other sites

  • Forum Moderator

This is the kind of thing that will inevitably cause me to spend time upgrading the client to full .tga instead of .sub when I will have something important to do instead. Thanks for this release, it will come in handy for sure!

  • Love 1

Gurgarath
coming soon

Link to comment
Share on other sites

  • 1 year later...
  • Honorable Member

#Update:

I've combined sanii187's SubSlicer tool with my tool

  • SubSlicer normally can't load dds files, uses png. Now If the dds file is wanted to be loaded with SubSlicer, it is firstly converted to png via my tool then loaded.
  • Now SubSlicer can export sub files via my tool.(Files and tools must be in same dir)

 

Preview

 

Edited by Mali
  • Metin2 Dev 8
  • Love 4

 

Link to comment
Share on other sites

  • Active+ Member

If you want to bulk crop here is a little python script I did some time ago:
 

Spoiler
import os
import scandir # pip install scandir
from PIL import Image # pip install Pillow

dirSource = "catchking" # the folder with the .sub
imageName = "king_001.dds" # the dds image

# Recursive scantree
def scantree(path):
	for entry in scandir.scandir(path):
		if entry.is_dir(follow_symlinks = False):
			for entry in scantree(entry.path):
				yield entry
		else:
			yield entry

def cropImage(img, cropArea, newFilename):
    croppedImage = img.crop(cropArea)
    croppedImage.save(newFilename)

def GetCropSourceFile(fileName):
	sourceName = ""
	with open(fileName, "rb") as f:
		for line in f:
			if line.startswith("image"):
				sourceName = str(line[6:])
				
	return sourceName.strip().replace('"', "")	
				
def GetCropAreFromFile(fileName):
	left, top, right, bottom = 0, 0, 0, 0
	with open(fileName, "rb") as f:
		for line in f:
			if line.startswith("left"):
				left = int(line[5:])
				
			if line.startswith("top"):
				top = int(line[4:])
				
			if line.startswith("right"):
				right = int(line[6:])
				
			if line.startswith("bottom"):
				bottom = int(line[7:])
			
	return (left, top, right, bottom)
	
def main():
	img = Image.open(imageName)
	for entry in scantree(dirSource):
		subName = entry.path
		root, ext = os.path.splitext(subName)

		if ext == ".sub":
			sourceName = GetCropSourceFile(subName)
			if sourceName != imageName:
				print "Can't crop sub %s because source name is not ok. (%s)" % (subName, sourceName)
				continue
				
			cropArea = GetCropAreFromFile(subName)

			print "Cropping %s - Area %s..." % (subName, str(cropArea))
			cropImage(img, cropArea, root + ".png")
			
			os.remove(subName)
			
if __name__ == "__main__":
    main()
	

 

 

  • Good 1
  • Love 2
Link to comment
Share on other sites

  • Premium

I did a program that creates sub files from given png files in directory. I find it really usefull as using dds files gain you better performence.
One I'll try to make a GUI for this to visualize results, but for me it's fine at current state as it gets the job done.

You need to convert output png canva to dds manually.

Spoiler
from pyglet import image
import os
import math

## https://metin2.dev/topic/21221-grid-class-python/
class Grid:
	def __init__(self, width, height):
		self.grid = [False] * (width * height)
		self.width = width
		self.height = height

	def __str__(self):
		output = "Grid {}x{} Information\n".format(self.width, self.height)
		
		for x in range(self.height):
			output += "%3d" % (x + 1)
		output += "\n"
		
		for row in range(self.height):
			output += "%3d" % (x +1)
			for col in range(self.width):
				output += "%3d" % (row * self.width + col)
				output += "  1" if self.grid[row * self.width + col] else "  0"
			output += "\n"

		return output

	def find_blank(self, width, height):
		if width > self.width or height > self.height:
			return -1, -1, -1

		for row in range(self.height):
			for col in range(self.width):
				index = row * self.width + col
				if self.is_empty(index, width, height):
					return index, row, col

		return -1, -1, -1

	def put(self, pos, width, height):
		if not self.is_empty(pos, width, height):
			return False

		for row in range(height):
			start = pos + (row * self.width)
			self.grid[start] = True
			col = 1
			while col < width:
				self.grid[start + col] = True
				col += 1

		return True

	def clear(self, pos, width, height):
		if pos < 0 or pos >= (self.width * self.height):
			return

		for row in range(height):
			start = pos + (row * self.width)
			self.grid[start] = True
			col = 1
			while col < width:
				self.grid[start + col] = False
				col += 1

	def is_empty(self, pos, width, height):
		if pos < 0:
			return False

		row = pos // self.width
		if (row + height) > self.height:
			return False

		if (pos + width) > ((row * self.width) + self.width):
			return False

		for row in range(height):
			start = pos + (row * self.width)
			if self.grid[start]:
				return False

			col = 1
			while col < width:
				if self.grid[start + col]:
					return False
				col += 1

		return True

	def get_size(self):
		return self.width * self.height

	def reset(self):
		self.grid = [False] * (self.width * self.height)

version = ""
while not (version.isdigit() and int(version) in (1, 2)):
	version = input("Select sub version (1 - load dds file from \"d:\\ymir work\\ui\\\" or 2 - load dds file from dir as sub files): ")

version = float(version)

ddsname = ""
while not ddsname:
	ddsname = input("Enter dds file name (public_offlineshop for example): ")

images = {}

files = [f for f in os.listdir('.') if os.path.isfile(f)]
for f in files:
	ext = f.split(".")[-1]
	if ext != "png":
		continue

	if f == f"{ddsname}.png":
		print(f"Skip {f}")
		continue

	print(f"Load file {f}")
	img = image.load(f)
	print("File loaded!")
	images[f] = img

def sortImg(img):
	width, height = img[1].width, img[1].height
	return width

images = sorted(images.items(), key=sortImg, reverse=True)
max_width, max_height = images[0][1].width, images[0][1].height

images = {key: value for key, value in images}

for key, img in images.items():
	width, height = img.width, img.height
	print(f"File name: {key}, image size: {width}x{height}")

## https://stackoverflow.com/questions/14267555/find-the-smallest-power-of-2-greater-than-or-equal-to-n-in-python
def next_power_of_2(x):
	return 1 if x == 0 else 2**math.ceil(math.log2(x))

canva_width, canva_height = next_power_of_2(max_width), next_power_of_2(max_height)
print(f"Likely size of canva: {canva_width}x{canva_height}")

done = 0
canva = None
while done < len(images):
	canva = image.Texture.create(width=canva_width, height=canva_height)
	grid = Grid(canva_width, canva_height)

	for key, img in images.items():
		img_width, img_height = img.width, img.height
		pos, x, y = grid.find_blank(img_width, img_height)

		if x >= 0 and y >= 0:
			grid.put(pos, img_width, img_height)
			done += 1
		try:
			canva.blit_into(img, y, x, 0)
			left = y
			top = canva_height - x - img_height
			right = left + img_width
			bottom = top + img_height
			print(f"Image size: {img_width}x{img_height}")
			print(f"Found position for image {key} on left {x}, right {x + img_width}, top {y}, bottom {y + img_height}")
			file_name = key.split(".")[0]
			with open(f"{file_name}.sub", "w") as f:
				f.write(f"title subImage\n")
				f.write(f"version {version:.1f}\n")
				f.write(f"image \"{ddsname}.dds\"\n")
				f.write(f"left {left}\ntop {top}\nright {right}\nbottom {bottom}\n")
				f.close()
		except:
			done = 0
			if math.log2(canva_height) + 1 >= math.log2(canva_width):
				canva_width *= 2
			else:
				canva_height *= 2
			print(f"Run out of blank space. Increase canva size to {canva_width}x{canva_height}")
			break

	canva.save(f"{ddsname}.png")

 

 

Edited by filipw1
  • Metin2 Dev 1
  • Good 1
Link to comment
Share on other sites

Announcements



  • Similar Content

  • Activity

    1. 0

      Target Information System

    2. 1

      Feeding game source to LLM

    3. 2

      anti exp explanation pls

    4. 1

      Feeding game source to LLM

    5. 2

      anti exp explanation pls

    6. 0

      [GR2] Positioning an object added with "Attach"

    7. 1417

      [40250] Reference Serverfile + Client + Src [15 Available Languages]

  • Recently Browsing

    • No registered users viewing this page.
×
×
  • Create New...

Important Information

Terms of Use / Privacy Policy / Guidelines / We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.