Difference between revisions of "Development/Tutorials/Games/Palapeli Slicers"

Jump to: navigation, search
(adjust link to libpala apidox)
m (Text replace - "<code cppqt n>" to "<syntaxhighlight lang="cpp-qt" line>")
Line 22: Line 22:
== The code: myslicer.h ==
== The code: myslicer.h ==
<code cppqt n>
<syntaxhighlight lang="cpp-qt" line>
#ifndef MYSLICER_H
#ifndef MYSLICER_H
#define MYSLICER_H
#define MYSLICER_H
Line 47: Line 47:
== The code: myslicer.cpp ==
== The code: myslicer.cpp ==
<code cppqt n>
<syntaxhighlight lang="cpp-qt" line>
#include "myslicer.h"
#include "myslicer.h"

Revision as of 20:31, 29 June 2011

Creating a slicer plugin for Palapeli
Tutorial Series   Programming with the Palapeli API
Previous   Introduction to KDE4 programming
What's Next   n/a
Further Reading   API reference for libpala


This tutorial shows you how to create a slicer for Palapeli, that is: a plugin for Palapeli that splits an image into pieces.

As an example for a very basic slicer, we will discuss the structure of the rectangle slicer, which splits the image into a configurable number of evenly-sized pieces.

Technical overview

Overview of the Palapeli infrastructure

Slicer writers do not have to bother with the changes that occur in the Palapeli application every now and then. Slicer plugins are not linked against Palapeli, but against libpala, a light-weight library that is designed for the sole purpose of slicing management. To Palapeli, it serves as an interface to talk with arbitrary slicer plugins. To slicer plugins, it provides an API to get and perform slicing jobs.

A slicer plugin needs to define a subclass of Pala::Slicer. Of course, you can also define more classes, but libpala will only talk to the single Pala::Slicer subclass.

The code: myslicer.h

  1 #ifndef MYSLICER_H
  2 #define MYSLICER_H
  4 #include <Pala/Slicer>
  5 #include <Pala/SlicerJob>
  6 #include <Pala/SlicerProperty>
  8 class MySlicer : public Pala::Slicer
  9 {
 10     Q_OBJECT
 11     public:
 12         MySlicer(QObject* parent = 0, const QVariantList& args = QVariantList());
 13         virtual bool run(Pala::SlicerJob* job);
 14 };
 16 #endif // MYSLICER_H
 17 </code>
 19 As described above, we need to create a subclass of <tt>Pala::Slicer</tt>. (We also include the other two classes from libpala, <tt>Pala::SlicerJob</tt> and <tt>Pala::SlicerProperty</tt>, which we'll be using in the code.) For this example, we only need to implement the minimum of two functions:
 20 * The constructor of the <tt>Pala::Slicer</tt> subclass needs to have '''exactly that signature''', because this constructor is called by the <tt>KPluginLoader</tt> in this way. The arguments need to be passed to the <tt>Pala::Slicer</tt> constructor, which might want to handle them. (You as a slicer developer will never have to bother with them.)
 21 * <tt>Pala::Slicer</tt> has one pure virtual method <tt>run()</tt>, which does the actual work.
 23 == The code: myslicer.cpp ==
 25 <syntaxhighlight lang="cpp-qt" line>
 26 #include "myslicer.h"
 28 #include <KLocalizedString>
 29 #include <KPluginFactory>
 30 #include <KPluginLoader>
 32 K_PLUGIN_FACTORY(MySlicerFactory, registerPlugin<MySlicer>();)
 33 K_EXPORT_PLUGIN(MySlicerFactory("myslicer"))
 35 MySlicer::MySlicer(QObject* parent, const QVariantList& args)
 36     : Pala::Slicer(parent, args)
 37 {
 38     Pala::SlicerProperty* prop;
 39     prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in horizontal direction"));
 40     prop->setRange(3, 100);
 41     prop->setDefaultValue(10);
 42     addProperty("XCount", prop);
 43     prop = new Pala::SlicerProperty(Pala::SlicerProperty::Integer, i18n("Piece count in vertical direction"));
 44     prop->setRange(3, 100);
 45     prop->setDefaultValue(10);
 46     addProperty("YCount", prop);
 47 }
 49 bool MySlicer::run(Pala::SlicerJob* job)
 50 {
 51     //read job
 52     const int xCount = job->argument("XCount").toInt();
 53     const int yCount = job->argument("YCount").toInt();
 54     const QImage image = job->image();
 55     //calculate some metrics
 56     const int pieceWidth = image.width() / xCount;
 57     const int pieceHeight = image.height() / yCount;
 58     const QSize pieceSize(pieceWidth, pieceHeight);
 59     //create pieces
 60     for (int x = 0; x < xCount; ++x)
 61     {
 62         for (int y = 0; y < yCount; ++y)
 63         {
 64             //calculate more metrics
 65             const QPoint offset(x * pieceWidth, y * pieceHeight);
 66             const QRect pieceBounds(offset, pieceSize);
 67             //copy image part to piece
 68             const QImage pieceImage = image.copy(pieceBounds);
 69             job->addPiece(x + y * xCount, pieceImage, offset);
 70         }
 71     }
 72     //create relations
 73     for (int x = 0; x < xCount; ++x)
 74     {
 75         for (int y = 0; y < yCount; ++y)
 76         {
 77             //along X axis (pointing left)
 78             if (x != 0)
 79                 job->addRelation(x + y * xCount, (x - 1) + y * xCount);
 80             //along Y axis (pointing up)
 81             if (y != 0)
 82                 job->addRelation(x + y * xCount, x + (y - 1) * xCount);
 83         }
 84     }
 85     return true;
 86 }
 88 #include "myslicer.moc"
 89 </code>
 91 The two <tt>KPlugin</tt> headers are necessary for the plugin integration, which happens in line 7 and 8. Note that the string constant in line 8 (<tt>"myslicer"</tt>) needs to match your library name.
 93 The constructor of a <tt>Pala::Slicer</tt> has two tasks: It needs to pass its arguments to the base class constructor, and define the properties of the slicer. Properties make it possible for the user to configure the slicer. In our case, we let the user choose how much pieces are generated. See the <a href="http://api.kde.org/playground-api/games-apidocs/palapeli/libpala/html/classPala_1_1SlicerProperty.html"><tt>Pala::SlicerProperty</tt></a> documentation for more details on what types of properties can be defined. (''Warning:'' libpala's slicer properties have nothing to do with QObject's meta properties.)
 95 We do not save pointers to the <tt>Pala::SlicerProperty</tt> instances that we add to the slicer. The properties are only passed internally to the application, which will construct an appropriate interface, and allow the user to enter values for the properties. When the user has configured everything, both the source image and the selected property values are packed into a <tt>Pala::SlicerJob</tt> object. The <tt>MySlicer::run</tt> method is then called for this job.
 97 In the <tt>run</tt> method, we read both the image and the property values from the job. After having calculated some metrics, we start to split the image into pieces. The process is straight-forward because all pieces are perfectly rectangular. When piece images are ready, we use the <tt>Pala::SlicerJob::addPiece</tt> method to add them to the result mass. Note that we need to define piece IDs for each piece (the first parameter of the <tt>addPiece</tt> call in line 44). These IDs can be arbitrary non-negative numbers, but have to be unique among all pieces.
 99 After we have added all pieces, we need to define neighbor relations between pieces. Neighbor relations are used to snap pieces together when they're near enough. Consider three pieces in a row: If piece 1 is near piece 3, nothing should snap because they do not have a common edge. If piece 1 is moved near piece 2, these should snap together. Therefore, we define a relation between piece 1 and piece 2 (and piece 2 and piece 3, respectively). The relation is added through the <tt>Pala::SlicerJob::addRelation</tt> method, which takes the IDs of two neighboring pieces. (The neighbor relation only needs to be defined in one direction: If piece 1 is a neighbor of piece 2, then piece 2 is also a neighbor of piece 1.)
101 We see that the <tt>Pala::SlicerJob</tt> object is used as a two-way communication channel between the slicer plugin and the application: Palapeli places the source image and the property values in it; the slicer reads these and writes the pieces and the relations into it; and in the end, Palapeli reads the pieces and the relations.
103 This simple implementation of a <tt>Pala::Slicer::run</tt> method always returns ''true''. You can return ''false'' if something went wrong during the slicing (e.&nbsp;g. because some external resources could not be located).
105 == Integrate into Palapeli: myslicer.desktop ==
107 <code>
108 [Desktop Entry]
109 Name=My very first slicer
110 Name[de]=Mein erstes Schnittmuster
111 Comment=It's quite simple, actually.
112 Comment[de]=Eigentlich ist das ganz einfach.
113 Type=Service
114 Icon=myslicer
115 X-KDE-Library=myslicer
116 X-KDE-ServiceTypes=Libpala/SlicerPlugin
117 X-KDE-PluginInfo-Author=Kandalf
118 X-KDE-PluginInfo-Email=kandalf@kde-hackers.example.org
119 X-KDE-PluginInfo-Name=myslicer
120 X-KDE-PluginInfo-Version=1.0
121 X-KDE-PluginInfo-Website=http://kde-hackers.example.org/myslicer
122 X-KDE-PluginInfo-Category=
123 X-KDE-PluginInfo-Depends=
124 X-KDE-PluginInfo-License=GPL
125 X-KDE-PluginInfo-EnabledByDefault=true
126 </code>
128 We need to tell the Palapeli library that there is a new plugin out there. We need a desktop entry file like shown here. You can mostly copy everything you see here; the most important thing is to keep the fields <tt>X-KDE-Library</tt> and <tt>X-KDE-PluginInfo-Name</tt> in sync with the library name (see next section). Note that the name is used in the "New puzzle" dialog to identify this pattern. (It can be translated; for example, a German translation has been added in the above example.)
130 == Build everything: CMakeLists.txt ==
132 <code>
133 project(myslicer)
134 find_package(KDE4 REQUIRED)
135 find_package(LibPala REQUIRED)
137 include(KDE4Defaults)
138 include_directories(${KDE4_INCLUDES} ${pala_INCLUDE_DIRS})
140 kde4_add_plugin(myslicer myslicer.cpp)
141 target_link_libraries(myslicer pala ${QT_QTGUI_LIBRARY} ${KDE4_KDECORE_LIBS})
144 install(FILES myslicer.desktop DESTINATION ${SERVICES_INSTALL_DIR})
145 </code>
147 Everything of this is pretty straightforward if you have previous experience with CMake (and this is what I assume). Make sure you compile everything as a plugin, and install it into the plugin directory. Do not forget to link against the "pala" target (which corresponds to libpala). After having installed the plugin, run <tt>kbuildsycoca4</tt>. This will locate your plugin, and enable Palapeli to use it. Here is how it should look like then:
149 [[Image:Palapeli Pattern Tutorial1.png]]

Content is available under Creative Commons License SA 4.0 unless otherwise noted.