Tile Grid: A plugin for creating customizable guide layouts for storyboards, tilesets, and more

Hello Krita artists!

I prototyped this small python plugin for my own storyboarding needs. This should run fine on Krita 5.2.5+.

Tile Grid” is a small plugin that lets you create a regular set of guides defining “tiles” (or “cells”) with customizable dimensions, aspect ratios, and spacing. It makes it easy to design basic tilesets, photoboards, storyboard layouts, or even single frames with user-defined margins.

The above screenshot defines a tile-grid layout of 3 x 3 tiles (1.780:1 ratio) centered on the canvas, with 10%-width and 15%-height margins, and a minimum spacing of 1.05 cm horizontally and 0.74 cm vertically. Four units are available: Pixels, Inches, Centimeters and Percentage.

The outcome would be a set of guides defining those 3 x 3 tiles to fill the canvas (minus margins) as much as possible. The horizontal and vertical spacing between the tiles (“gutters”) are automatically calculated to let the tiles fit within the allocated space while maintaining their format ratio.

I personally use it to quickly create storyboard layout sheets like this:

Those guides help to precisely draw frames (included unions of frames when necessary), and help to create rectangular selections to cleanup the outside of the frames once the drawing is complete.

You can download the latest version of the plugin on Github.

Well… hope some of you find it useful. :slightly_smiling_face:

27 Likes

Thank you for sharing this, @madjyc. I’m sure many Krita users will find it useful.

2 Likes

Thank you very much for the plugin! I’m using Krita 5.2.6 (git ab3502b) and I didn’t need step 3 to install the plugin: Krita itself asks if you want to activate the plugin after restarting… and when I reopened Krita, the plugin was already listed under “Scripts”.

I tested it with a file of 3200 x 1900 pixels. That’s the total size, with bleed (in the dark gray areas), and the crop area is 2560 x 1440 pixels. I wanted to divide the crop area into fifteen equal parts, both in width and height:

With this size I can draw grids for composition, like these:

And also to establish what Andrew Loomis called Focal Point:

1 Like

@Guerreiro64 Ah, good to know, thanks! I’ll correct the procedure in the Github page ASAP.

So did it work for you? I tested your settings on my side and it worked as intended (i.e. margins + 15 equal subdivisions).

EDIT: Oops sorry, your fist screenshot shows it did. I couldn’t see the margins in the gray surrounding area on my laptop! :face_with_hand_over_mouth:

This is so super useful for making grids for comic pages!

Unfortunately it worked the first time, but now does not want to run anymore…

It’s something with the default preset.
In the traceback it says:

args = ("could not convert string to float: '2,00'",)

Weirdly it still does that even if I delete the script and reinstall it. Where does it save the defaults?

Here’s the complete output:

ValueError
Python 3.10.7: /Applications/krita.app/Contents/MacOS/krita
Wed Jan 15 21:39:17 2025

A problem occurred in a Python script.  Here is the sequence of
function calls leading up to the error, in the order they occurred.

 ~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py in add_tile_grid(self=<tile_grid.tile_grid.TileGridExtension object>)
  479         doc_ppi = doc.resolution()
  480 
  481         dialog = TileGridDialog(doc_size_x, doc_size_y, doc_ppi)
  482         if not dialog.exec_() == QDialog.Accepted:
  483             return
dialog undefined
global TileGridDialog = <class 'tile_grid.tile_grid.TileGridDialog'>
doc_size_x = 2480
doc_size_y = 3508
doc_ppi = 300

 ~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py in __init__(self=<tile_grid.tile_grid.TileGridDialog object>, doc_size_x=2480, doc_size_y=3508, doc_ppi=300)
  234 
  235         # Load the last used preset on initialization
  236         self.load_last_preset()
  237 
  238         # Connect the comboboxes to on_combobox_index_changed with an indirection to allow more parameters
self = <tile_grid.tile_grid.TileGridDialog object>
self.load_last_preset = <bound method TileGridDialog.load_last_preset of <tile_grid.tile_grid.TileGridDialog object>>

~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py in load_last_preset(self=<tile_grid.tile_grid.TileGridDialog object>)
  401             with open(last_preset_path, 'r') as file:
  402                 preset = json.load(file)
  403                 self.apply_preset(preset)
  404         except FileNotFoundError:
  405             self.default_preset()
self = <tile_grid.tile_grid.TileGridDialog object>
self.apply_preset = <bound method TileGridDialog.apply_preset of <tile_grid.tile_grid.TileGridDialog object>>
preset = {'clear_guides': True, 'gutter_x': '0,50', 'gutter_x_unit': 'Centimeter (cm)', 'gutter_y': '0,40', 'gutter_y_unit': 'Centimeter (cm)', 'lock_guides': True, 'margin_b': '3,00', 'margin_b_unit': 'Centimeter (cm)', 'margin_l': '2,00', 'margin_l_unit': 'Centimeter (cm)', ...}

 ~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py in apply_preset(self=<tile_grid.tile_grid.TileGridDialog object>, preset={'clear_guides': True, 'gutter_x': '0,50', 'gutter_x_unit': 'Centimeter (cm)', 'gutter_y': '0,40', 'gutter_y_unit': 'Centimeter (cm)', 'lock_guides': True, 'margin_b': '3,00', 'margin_b_unit': 'Centimeter (cm)', 'margin_l': '2,00', 'margin_l_unit': 'Centimeter (cm)', ...})
  377 
  378         # Set the values of the spinboxes and checkboxes
  379         self.margin_l.setValue(float(preset.get("margin_l", self.DEFAULT_MARGIN_L_PC)))
  380         self.margin_r.setValue(float(preset.get("margin_r", self.DEFAULT_MARGIN_R_PC)))
  381         self.margin_t.setValue(float(preset.get("margin_t", self.DEFAULT_MARGIN_T_PC)))
self = <tile_grid.tile_grid.TileGridDialog object>
self.margin_l = <PyQt5.QtWidgets.QDoubleSpinBox object>
self.margin_l.setValue = <built-in method setValue of QDoubleSpinBox object>
builtinfloat = <class 'float'>
preset = {'clear_guides': True, 'gutter_x': '0,50', 'gutter_x_unit': 'Centimeter (cm)', 'gutter_y': '0,40', 'gutter_y_unit': 'Centimeter (cm)', 'lock_guides': True, 'margin_b': '3,00', 'margin_b_unit': 'Centimeter (cm)', 'margin_l': '2,00', 'margin_l_unit': 'Centimeter (cm)', ...}
preset.get = <built-in method get of dict object>
self.DEFAULT_MARGIN_L_PC = 10.0
ValueError: could not convert string to float: '2,00'
    __cause__ = None
    __class__ = <class 'ValueError'>
    __context__ = None
    __delattr__ = <method-wrapper '__delattr__' of ValueError object>
    __dict__ = {}
    __dir__ = <built-in method __dir__ of ValueError object>
    __doc__ = 'Inappropriate argument value (of correct type).'
    __eq__ = <method-wrapper '__eq__' of ValueError object>
    __format__ = <built-in method __format__ of ValueError object>
    __ge__ = <method-wrapper '__ge__' of ValueError object>
    __getattribute__ = <method-wrapper '__getattribute__' of ValueError object>
    __gt__ = <method-wrapper '__gt__' of ValueError object>
    __hash__ = <method-wrapper '__hash__' of ValueError object>
    __init__ = <method-wrapper '__init__' of ValueError object>
    __init_subclass__ = <built-in method __init_subclass__ of type object>
    __le__ = <method-wrapper '__le__' of ValueError object>
    __lt__ = <method-wrapper '__lt__' of ValueError object>
    __ne__ = <method-wrapper '__ne__' of ValueError object>
    __new__ = <built-in method __new__ of type object>
    __reduce__ = <built-in method __reduce__ of ValueError object>
    __reduce_ex__ = <built-in method __reduce_ex__ of ValueError object>
    __repr__ = <method-wrapper '__repr__' of ValueError object>
    __setattr__ = <method-wrapper '__setattr__' of ValueError object>
    __setstate__ = <built-in method __setstate__ of ValueError object>
    __sizeof__ = <built-in method __sizeof__ of ValueError object>
    __str__ = <method-wrapper '__str__' of ValueError object>
    __subclasshook__ = <built-in method __subclasshook__ of type object>
    __suppress_context__ = False
    __traceback__ = <traceback object>
    args = ("could not convert string to float: '2,00'",)
    with_traceback = <built-in method with_traceback of ValueError object>

The above is a description of an error in a Python program.  Here is
the original traceback:

Traceback (most recent call last):
  File "~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py", line 481, in add_tile_grid
    dialog = TileGridDialog(doc_size_x, doc_size_y, doc_ppi)
  File "~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py", line 236, in __init__
    self.load_last_preset()
  File "~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py", line 403, in load_last_preset
    self.apply_preset(preset)
  File "~/Library/Application Support/krita/pykrita/tile_grid/tile_grid.py", line 379, in apply_preset
    self.margin_l.setValue(float(preset.get("margin_l", self.DEFAULT_MARGIN_L_PC)))
ValueError: could not convert string to float: '2,00'

Thanks @Papernoise for reporting the issue.

At first glance, the problem seems to come from the values saved in the json file. This ‘,’ (comma) character looks suspicious, it should be ‘.’ (dot) instead.

The default json file is at the root of C:\User<your_user_name>. To get there, just type %USERPROFILE% in Explorer, then delete the file named “krita_tile_grid_plugin_last_preset.json”. The plugin will create a new one next time it is run.

I’ll look into it and come back to you ASAP.

1 Like

This bleed area is actually black, I usually lower its opacity to 63 - 50% so I can work with it.

I should have taken a screenshot earlier to make it clearer…but I’ll do this now:

1 Like

@Papernoise That was it! Thanks for sending the full error message.

I’ve just uploaded version 0.1.2 on Github. Please delete the default json file as explained in my previous post and download/install the latest version of the plugin. Saving and loading presets should now work as expected in all languages.

Please let me know if you still encounter an error.

1 Like

@Guerreiro64 Thanks again for your feedback, I’ve updated the installation instructions on Github as you suggested.

Krita 2.5.6 makes plugin installation really easy!

2 Likes

Thanks! Deleting the .json file did fix it!
We’re using commas as a float delimiter here not dots, and indeed the plugin by default shows up like this:

Yes it does ideed!

Everything works fine now! Thanks!!!

I have a question, though:
The way the plug-in works seems to be that you set up the ratio of the tile you want to create and then the script works out the gutters. I would much rather have it work the other way. For comic panels you’ll want to define a precise gutter width, tell it how many columns and rows to make and then define the width and height of the tiles based on that instead of the ratio. Right now that does not seem to be possible, or is it?
update: it just occurred to me that if I add a ridiculously high ratio, it will work! :grinning:
update2: ok no it doesn’t work. it will create a weird grid. posting some examples below.

1 Like

So here’s my issues with making a comic page grid.

Let’s say I have a page and want to divide it into 3 colums and 4 rows and have a 0.4cm gutter horizontally and maybe 0.5cm on the vertical axis.

Here’s the settings:

I then get this:

It kinda works, but the vertical gutter is too much, since the script will always stick to the ratio, and indeed the tiles are squares here.

I need to go by trail and error with the ratio until I figure out the right ratio for the panels here, which is a bit cumbersome to be honest. I know this makes a lot of sense for storyboards, but for comic pages this is not ideal unfortunately.

Hmm… As you said, the plugin is meant to favor tile ratio over gutter sizes, but (sorry I’m thinking out loud) I could use instead both fixed ratio and gutter sizes. That would mean typing a specific ratio would automatically calculate and display the correct gutter sizes, and vice versa. I’ll see what I can do and I’ll come back to you. No promises though, that might lead to big changes in the code…

@Papernoise OK, Tile Grid 0.1.3 is online on Github. This version should fix your issue with dynamic gutters.

Now the tile format ratio is automatically recalculated whenever any other value is changed. So unless you change the format ratio yourself, the gutter sizes will be considered as fixed values, since the format ratio has adapted to them.

However, if you do need a specific format ratio (e.g. storyboard), just enter that ratio by hand. The gutter sizes will then be considered as hints (i.e. minimum values) as before and adapt to the actual size of the tiles.

As an example, the settings you provided would look like this. Note how the format ratio has adapted to the other values (0.920).

As the format ratio has been calculated to conform to all other values, there’s no difference between minimum gutter sizes and absolute gutter sizes.

Hope this change will offer the best of both worlds without too much hassle. :slightly_smiling_face:

Please let me know if this now works for you.

1 Like

Amazing!!! Thank you so much! Works like a charm now, and I think this is a brilliant solution that gives people all the options!

This now solved one of the major challenges with setting up comic pages in Krita!

1 Like

i love it. a precise grid without any efford. Thank you.

1 Like

I am going to leave you with a few tips. If you want to create two guides in the center of the image, just leave all the values at zero in “Margins and Gutters”, enter the number 2 in “Columns” and “Rows” and click “OK”.

To create a Rule of Thirds (often used in composition) with the guides, it’s almost the same thing, just enter the number 3 instead of 2:

3 Likes

Hey all!

Just to let you know that Tile Grid 0.1.4 is online on GitHub. Nothing new really, only a couple of tiny-tiny tweaks.

  1. The Format ratio (w/h) spinbox now displays 5 decimals instead of 3, for a weeeee bit of (imperceptible) added precision. Just in case.

  2. After some thought, I removed the Snap to guides checkbox. Afaik, there is currently no way for a Python plugin to check whether the View > Snap To > Snap to Guides options is enabled in Krita, so the checkbox was acting as a toggle switch, (unlike the more predictable behavior of the ‘Clear guides’ and ‘Lock guides’ options). I figured that was more confusing than helpful. That said, the underlying code is still there — it’s just hidden for now, in case it becomes useful later.

Happy gridding!

2 Likes