Reorganized all resources to a subdirectory structure and added the CMake install prefix to the resource search path.

CMAKE_INSTALL_PREFIX and CMAKE_INSTALL_DATAROOTDIR are now used to resolve the resource path. As of this commit, there are only two paths where resources are searched, under the user home directory and under this install prefix directory (which defaults to /usr/local/share/emulationstation/resources but can be set to for instance /opt/share/emulationstation/resources using the appropriate CMake flags).
This commit is contained in:
Leon Styhre 2020-06-21 19:35:43 +02:00
parent 67aa6b3dbd
commit 8fefc9232c
64 changed files with 76 additions and 61 deletions

View file

@ -119,6 +119,10 @@ else()
add_definitions(-DUSE_OPENGLES_10)
endif()
#add installation prefix and datarootdir
add_definitions(-DES_INSTALL_PREFIX="${CMAKE_INSTALL_PREFIX}")
add_definitions(-DES_DATAROOTDIR="${CMAKE_INSTALL_DATAROOTDIR}")
# Enable additional defines for the Debug build configuration
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -D_DEBUG")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -D_DEBUG")

View file

@ -57,7 +57,7 @@ void GuiCollectionSystemsOptions::initializeMenu()
ComponentListRow row;
row.addElement(std::make_shared<TextComponent>(mWindow, "CREATE NEW CUSTOM COLLECTION", Font::get(FONT_SIZE_MEDIUM), 0x777777FF), true);
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/arrow.svg");
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(Vector2f(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
row.addElement(bracket, false);
auto createCustomCollection = [this](const std::string& newVal) {

View file

@ -22,7 +22,7 @@ GuiGameScraper::GuiGameScraper(
std::function<void(const ScraperSearchResult&)> doneFunc)
: GuiComponent(window),
mGrid(window, Vector2i(1, 7)),
mBox(window, ":/frame.png"),
mBox(window, ":/graphics/frame.png"),
mSearchParams(params),
mClose(false)
{

View file

@ -44,7 +44,7 @@ GuiInfoPopup::GuiInfoPopup(Window* window, std::string message, int duration) :
setPosition(posX, posY, 0);
mFrame->setImagePath(":/frame.png");
mFrame->setImagePath(":/graphics/frame.png");
mFrame->fitTo(mSize, Vector3f::Zero(), Vector2f(-32, -32));
addChild(mFrame);

View file

@ -40,7 +40,7 @@ GuiMetaDataEd::GuiMetaDataEd(
: GuiComponent(window),
mScraperParams(scraperParams),
mBackground(window, ":/frame.png"),
mBackground(window, ":/graphics/frame.png"),
mGrid(window, Vector2i(1, 3)),
mMetaDataDecl(mdd),
@ -146,7 +146,7 @@ GuiMetaDataEd::GuiMetaDataEd(
row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/arrow.svg");
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight()));
row.addElement(bracket, false);
@ -179,7 +179,7 @@ GuiMetaDataEd::GuiMetaDataEd(
row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/arrow.svg");
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight()));
row.addElement(bracket, false);

View file

@ -25,7 +25,7 @@ GuiScraperMulti::GuiScraperMulti(
const std::queue<ScraperSearchParams>& searches,
bool approveResults)
: GuiComponent(window),
mBackground(window, ":/frame.png"),
mBackground(window, ":/graphics/frame.png"),
mGrid(window, Vector2i(1, 5)),
mSearchQueue(searches)
{

View file

@ -74,7 +74,7 @@ void GuiScreensaverOptions::addEditableTextComponent(ComponentListRow row, const
row.addElement(spacer, false);
auto bracket = std::make_shared<ImageComponent>(mWindow);
bracket->setImage(":/arrow.svg");
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(Vector2f(0, lbl->getFont()->getLetterHeight()));
row.addElement(bracket, false);

View file

@ -42,7 +42,7 @@ MameNames* MameNames::getInstance()
MameNames::MameNames()
{
std::string xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamenames.xml");
std::string xmlpath = ResourceManager::getInstance()->getResourcePath(":/MAME/mamenames.xml");
if (!Utils::FileSystem::exists(xmlpath))
return;
@ -68,7 +68,7 @@ MameNames::MameNames()
}
// Read BIOS file.
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamebioses.xml");
xmlpath = ResourceManager::getInstance()->getResourcePath(":/MAME/mamebioses.xml");
if (!Utils::FileSystem::exists(xmlpath))
return;
@ -90,7 +90,7 @@ MameNames::MameNames()
}
// Read devices file.
xmlpath = ResourceManager::getInstance()->getResourcePath(":/mamedevices.xml");
xmlpath = ResourceManager::getInstance()->getResourcePath(":/MAME/mamedevices.xml");
if (!Utils::FileSystem::exists(xmlpath))
return;

View file

@ -96,7 +96,7 @@ bool Window::init()
mDefaultFonts.push_back(Font::get(FONT_SIZE_LARGE));
}
mBackgroundOverlay->setImage(":/scroll_gradient.png");
mBackgroundOverlay->setImage(":/graphics/scroll_gradient.png");
mBackgroundOverlay->setResize((float)Renderer::getScreenWidth(),
(float)Renderer::getScreenHeight());
@ -309,7 +309,7 @@ void Window::renderLoadingScreen(std::string text)
ImageComponent splash(this, true);
splash.setResize(Renderer::getScreenWidth() * 0.6f, 0.0f);
splash.setImage(":/splash.svg");
splash.setImage(":/graphics/splash.svg");
splash.setPosition((Renderer::getScreenWidth() - splash.getSize().x()) / 2,
(Renderer::getScreenHeight() - splash.getSize().y()) / 2 * 0.6f);
splash.render(trans);

View file

@ -6,15 +6,15 @@
// animation definition
AnimationFrame BUSY_ANIMATION_FRAMES[] = {
{":/busy_0.svg", 300},
{":/busy_1.svg", 300},
{":/busy_2.svg", 300},
{":/busy_3.svg", 300},
{":/graphics/busy_0.svg", 300},
{":/graphics/busy_1.svg", 300},
{":/graphics/busy_2.svg", 300},
{":/graphics/busy_3.svg", 300},
};
const AnimationDef BUSY_ANIMATION_DEF = { BUSY_ANIMATION_FRAMES, 4, true };
BusyComponent::BusyComponent(Window* window) : GuiComponent(window),
mBackground(window, ":/frame.png"), mGrid(window, Vector2i(5, 3))
mBackground(window, ":/graphics/frame.png"), mGrid(window, Vector2i(5, 3))
{
mAnimation = std::make_shared<AnimatedImageComponent>(mWindow);
mAnimation->load(&BUSY_ANIMATION_DEF);

View file

@ -4,7 +4,7 @@
#include "utils/StringUtil.h"
ButtonComponent::ButtonComponent(Window* window, const std::string& text, const std::string& helpText, const std::function<void()>& func) : GuiComponent(window),
mBox(window, ":/button.png"),
mBox(window, ":/graphics/button.png"),
mFont(Font::get(FONT_SIZE_MEDIUM)),
mFocused(false),
mEnabled(true),
@ -72,7 +72,7 @@ void ButtonComponent::updateImage()
{
if(!mEnabled || !mPressedFunc)
{
mBox.setImagePath(":/button_filled.png");
mBox.setImagePath(":/graphics/button_filled.png");
mBox.setCenterColor(0x770000FF);
mBox.setEdgeColor(0x770000FF);
return;
@ -80,7 +80,7 @@ void ButtonComponent::updateImage()
mBox.setCenterColor(0xFFFFFFFF);
mBox.setEdgeColor(0xFFFFFFFF);
mBox.setImagePath(mFocused ? ":/button_filled.png" : ":/button.png");
mBox.setImagePath(mFocused ? ":/graphics/button_filled.png" : ":/graphics/button.png");
}
void ButtonComponent::render(const Transform4x4f& parentTrans)

View file

@ -9,7 +9,7 @@ GridTileComponent::GridTileComponent(Window* window) : GuiComponent(window), mBa
mDefaultProperties.mSize = getDefaultTileSize();
mDefaultProperties.mPadding = Vector2f(16.0f, 16.0f);
mDefaultProperties.mImageColor = 0xAAAAAABB;
mDefaultProperties.mBackgroundImage = ":/frame.png";
mDefaultProperties.mBackgroundImage = ":/graphics/frame.png";
mDefaultProperties.mBackgroundCornerSize = Vector2f(16 ,16);
mDefaultProperties.mBackgroundCenterColor = 0xAAAAEEFF;
mDefaultProperties.mBackgroundEdgeColor = 0xAAAAEEFF;

View file

@ -89,7 +89,7 @@ public:
mTitleOverlayOpacity = 0x00;
mTitleOverlayColor = 0xFFFFFF00;
mGradient.setResize((float)Renderer::getScreenWidth(), (float)Renderer::getScreenHeight());
mGradient.setImage(":/scroll_gradient.png");
mGradient.setImage(":/graphics/scroll_gradient.png");
mTitleOverlayFont = Font::get(FONT_SIZE_LARGE);
}

View file

@ -123,8 +123,8 @@ ImageGridComponent<T>::ImageGridComponent(Window* window) : IList<ImageGridData,
mEntriesDirty = true;
mLastCursor = 0;
mDefaultGameTexture = ":/cartridge.svg";
mDefaultFolderTexture = ":/folder.svg";
mDefaultGameTexture = ":/graphics/cartridge.svg";
mDefaultFolderTexture = ":/graphics/folder.svg";
mSize = screen * 0.80f;
mMargin = screen * 0.07f;

View file

@ -25,7 +25,7 @@ MenuComponent::MenuComponent(
addChild(&mBackground);
addChild(&mGrid);
mBackground.setImagePath(":/frame.png");
mBackground.setImagePath(":/graphics/frame.png");
// Set up title.
mTitle = std::make_shared<TextComponent>(mWindow);
@ -159,7 +159,7 @@ std::shared_ptr<ComponentGrid> makeButtonGrid(Window* window,
std::shared_ptr<ImageComponent> makeArrow(Window* window)
{
auto bracket = std::make_shared<ImageComponent>(window);
bracket->setImage(":/arrow.svg");
bracket->setImage(":/graphics/arrow.svg");
bracket->setResize(0, Math::round(Font::get(FONT_SIZE_MEDIUM)->getLetterHeight()));
return bracket;
}

View file

@ -9,8 +9,8 @@
#ifndef ES_CORE_COMPONENTS_OPTION_LIST_COMPONENT_H
#define ES_CORE_COMPONENTS_OPTION_LIST_COMPONENT_H
#define CHECKED_PATH ":/checkbox_checked.svg"
#define UNCHECKED_PATH ":/checkbox_unchecked.svg"
#define CHECKED_PATH ":/graphics/checkbox_checked.svg"
#define UNCHECKED_PATH ":/graphics/checkbox_unchecked.svg"
#include "GuiComponent.h"
#include "Log.h"
@ -51,15 +51,15 @@ public:
mRightArrow.setResize(0, mText.getFont()->getLetterHeight());
if(mMultiSelect) {
mRightArrow.setImage(":/arrow.svg");
mRightArrow.setImage(":/graphics/arrow.svg");
addChild(&mRightArrow);
}
else {
mLeftArrow.setImage(":/option_arrow.svg");
mLeftArrow.setImage(":/graphics/option_arrow.svg");
mLeftArrow.setFlipX(true);
addChild(&mLeftArrow);
mRightArrow.setImage(":/option_arrow.svg");
mRightArrow.setImage(":/graphics/option_arrow.svg");
addChild(&mRightArrow);
}

View file

@ -14,8 +14,8 @@
RatingComponent::RatingComponent(Window* window) : GuiComponent(window),
mColorShift(0xFFFFFFFF), mColorShiftEnd(0xFFFFFFFF), mUnfilledColor(0xFFFFFFFF)
{
mFilledTexture = TextureResource::get(":/star_filled.svg", true);
mUnfilledTexture = TextureResource::get(":/star_unfilled.svg", true);
mFilledTexture = TextureResource::get(":/graphics/star_filled.svg", true);
mUnfilledTexture = TextureResource::get(":/graphics/star_unfilled.svg", true);
mValue = 0.5f;
mSize = Vector2f(64 * NUM_RATING_STARS, 64);
updateVertices();

View file

@ -14,7 +14,7 @@ SliderComponent::SliderComponent(Window* window, float min, float max, float inc
mValue = (max + min) / 2;
mKnob.setOrigin(0.5f, 0.5f);
mKnob.setImage(":/slider_knob.svg");
mKnob.setImage(":/graphics/slider_knob.svg");
setSize(Renderer::getScreenWidth() * 0.15f, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight());
}

View file

@ -15,7 +15,7 @@ SwitchComponent::SwitchComponent(
mImage(window),
mState(state)
{
mImage.setImage(":/off.svg");
mImage.setImage(":/graphics/off.svg");
mImage.setResize(0, Font::get(FONT_SIZE_MEDIUM)->getLetterHeight());
mSize = mImage.getSize();
}
@ -70,7 +70,7 @@ void SwitchComponent::setValue(const std::string& statestring)
void SwitchComponent::onStateChanged()
{
mImage.setImage(mState ? ":/on.svg" : ":/off.svg");
mImage.setImage(mState ? ":/graphics/on.svg" : ":/graphics/off.svg");
}
std::vector<HelpPrompt> SwitchComponent::getHelpPrompts()

View file

@ -18,7 +18,7 @@
TextEditComponent::TextEditComponent(
Window* window)
: GuiComponent(window),
mBox(window, ":/textinput_ninepatch.png"),
mBox(window, ":/graphics/textinput_ninepatch.png"),
mFocused(false),
mScrollOffset(0.0f, 0.0f),
mCursor(0), mEditing(false),
@ -33,13 +33,13 @@ TextEditComponent::TextEditComponent(
void TextEditComponent::onFocusGained()
{
mFocused = true;
mBox.setImagePath(":/textinput_ninepatch_active.png");
mBox.setImagePath(":/graphics/textinput_ninepatch_active.png");
}
void TextEditComponent::onFocusLost()
{
mFocused = false;
mBox.setImagePath(":/textinput_ninepatch.png");
mBox.setImagePath(":/graphics/textinput_ninepatch.png");
}
void TextEditComponent::onSizeChanged()

View file

@ -27,7 +27,7 @@ GuiComplexTextEditPopup::GuiComplexTextEditPopup(
const char* saveConfirmationText)
: GuiComponent(window),
mHelpStyle(helpstyle),
mBackground(window, ":/frame.png"),
mBackground(window, ":/graphics/frame.png"),
mGrid(window, Vector2i(1, 5)),
mMultiLine(multiLine),
mInitValue(initValue),

View file

@ -11,7 +11,7 @@
#define HOLD_TIME 1000
GuiDetectDevice::GuiDetectDevice(Window* window, bool firstRun, const std::function<void()>& doneCallback) : GuiComponent(window), mFirstRun(firstRun),
mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 5))
mBackground(window, ":/graphics/frame.png"), mGrid(window, Vector2i(1, 5))
{
mHoldingConfig = NULL;
mHoldTime = 0;

View file

@ -51,7 +51,7 @@ static const InputConfigStructure GUI_INPUT_CONFIG_LIST[inputCount] =
#define HOLD_TO_SKIP_MS 1000
GuiInputConfig::GuiInputConfig(Window* window, InputConfig* target, bool reconfigureAll, const std::function<void()>& okCallback) : GuiComponent(window),
mBackground(window, ":/frame.png"), mGrid(window, Vector2i(1, 7)),
mBackground(window, ":/graphics/frame.png"), mGrid(window, Vector2i(1, 7)),
mTargetConfig(target), mHoldingInput(false), mBusyAnim(window)
{
LOG(LogInfo) << "Configuring device " << target->getDeviceId() << " (" << target->getDeviceName() << ").";

View file

@ -18,7 +18,7 @@ GuiMsgBox::GuiMsgBox(Window* window, const HelpStyle& helpstyle, const std::stri
const std::string& name3, const std::function<void()>& func3)
: GuiComponent(window),
mHelpStyle(helpstyle),
mBackground(window, ":/frame.png"),
mBackground(window, ":/graphics/frame.png"),
mGrid(window, Vector2i(1, 2))
{
float width = Renderer::getScreenWidth() * 0.6f; // Max width.

View file

@ -23,7 +23,7 @@ GuiTextEditPopup::GuiTextEditPopup(
const char* saveConfirmationText)
: GuiComponent(window),
mHelpStyle(helpstyle),
mBackground(window, ":/frame.png"),
mBackground(window, ":/graphics/frame.png"),
mGrid(window, Vector2i(1, 3)),
mMultiLine(multiLine),
mInitValue(initValue),

View file

@ -27,7 +27,7 @@ namespace Renderer
{
size_t width = 0;
size_t height = 0;
ResourceData resData = ResourceManager::getInstance()->getFileData(":/window_icon_256.png");
ResourceData resData = ResourceManager::getInstance()->getFileData(":/graphics/window_icon_256.png");
std::vector<unsigned char> rawData = ImageIO::loadFromMemoryRGBA32(resData.ptr.get(), resData.length, width, height);
if(!rawData.empty())

View file

@ -253,19 +253,19 @@ std::vector<std::string> getFallbackFontPaths()
// Vera sans Unicode:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/DejaVuSans.ttf"));
getResourcePath(":/fonts/DejaVuSans.ttf"));
// Freefont monospaced:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/FreeMono.ttf"));
getResourcePath(":/fonts/FreeMono.ttf"));
// Various languages, such as Japanese and Chinese:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/DroidSansFallbackFull.ttf"));
getResourcePath(":/fonts/DroidSansFallbackFull.ttf"));
// Korean:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/NanumMyeongjo.ttf"));
getResourcePath(":/fonts/NanumMyeongjo.ttf"));
// Font Awesome icon glyphs, used for star-flagging favorites in the gamelists:
fontPaths.push_back(ResourceManager::getInstance()->
getResourcePath(":/fontawesome-webfont.ttf"));
getResourcePath(":/fonts/fontawesome-webfont.ttf"));
fontPaths.shrink_to_fit();
return fontPaths;

View file

@ -29,8 +29,8 @@ class TextCache;
#define FONT_SIZE_LARGE ((unsigned int)(0.085f * Math::min((int)Renderer::getScreenHeight(), \
(int)Renderer::getScreenWidth())))
#define FONT_PATH_LIGHT ":/opensans_hebrew_condensed_light.ttf"
#define FONT_PATH_REGULAR ":/opensans_hebrew_condensed_regular.ttf"
#define FONT_PATH_LIGHT ":/fonts/opensans_hebrew_condensed_light.ttf"
#define FONT_PATH_REGULAR ":/fonts/opensans_hebrew_condensed_regular.ttf"
enum Alignment {
ALIGN_LEFT,

View file

@ -10,6 +10,18 @@
#include "utils/FileSystemUtil.h"
#include <fstream>
#ifdef ES_INSTALL_PREFIX
std::string installPrefix = ES_INSTALL_PREFIX;
#else
std::string installPrefix = "/usr/local";
#endif
#ifdef ES_DATAROOTDIR
std::string dataRootDir = ES_DATAROOTDIR;
#else
std::string dataRootDir = "share";
#endif
auto array_deleter = [](unsigned char* p) { delete[] p; };
auto nop_deleter = [](unsigned char* /*p*/) { };
@ -33,18 +45,17 @@ std::string ResourceManager::getResourcePath(const std::string& path) const
if ((path[0] == ':') && (path[1] == '/')) {
std::string test;
// Check in homepath.
// Check under the home directory.
test = Utils::FileSystem::getHomePath() + "/.emulationstation/resources/" + &path[2];
if (Utils::FileSystem::exists(test))
return test;
// Check in exepath.
test = Utils::FileSystem::getExePath() + "/resources/" + &path[2];
if (Utils::FileSystem::exists(test))
return test;
// Check in cwd.
test = Utils::FileSystem::getCWDPath() + "/resources/" + &path[2];
// Check under the data installation directory.
// The base directory is the value set for CMAKE_INSTALL_DIRECTORY during build.
// The datarootdir directory is the value set for CMAKE_INSTALL_DATAROOTDIR during build.
// If not defined, the default prefix path '/usr/local' and the default datarootdir
// directory 'share' will be used, i.e. '/usr/local/share'.
test = installPrefix + "/" + dataRootDir + "/emulationstation/resources/" + &path[2];
if (Utils::FileSystem::exists(test))
return test;
}

View file

Before

Width:  |  Height:  |  Size: 268 B

After

Width:  |  Height:  |  Size: 268 B

View file

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View file

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View file

Before

Width:  |  Height:  |  Size: 482 B

After

Width:  |  Height:  |  Size: 482 B

View file

Before

Width:  |  Height:  |  Size: 483 B

After

Width:  |  Height:  |  Size: 483 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

View file

Before

Width:  |  Height:  |  Size: 479 B

After

Width:  |  Height:  |  Size: 479 B

View file

Before

Width:  |  Height:  |  Size: 335 B

After

Width:  |  Height:  |  Size: 335 B

View file

Before

Width:  |  Height:  |  Size: 286 B

After

Width:  |  Height:  |  Size: 286 B

View file

Before

Width:  |  Height:  |  Size: 215 B

After

Width:  |  Height:  |  Size: 215 B

View file

Before

Width:  |  Height:  |  Size: 495 B

After

Width:  |  Height:  |  Size: 495 B

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 1.3 KiB

After

Width:  |  Height:  |  Size: 1.3 KiB

View file

Before

Width:  |  Height:  |  Size: 1.7 KiB

After

Width:  |  Height:  |  Size: 1.7 KiB

View file

Before

Width:  |  Height:  |  Size: 247 B

After

Width:  |  Height:  |  Size: 247 B

View file

Before

Width:  |  Height:  |  Size: 81 KiB

After

Width:  |  Height:  |  Size: 81 KiB

View file

Before

Width:  |  Height:  |  Size: 190 B

After

Width:  |  Height:  |  Size: 190 B

View file

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 19 KiB

View file

Before

Width:  |  Height:  |  Size: 807 B

After

Width:  |  Height:  |  Size: 807 B

View file

Before

Width:  |  Height:  |  Size: 807 B

After

Width:  |  Height:  |  Size: 807 B

View file

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 1.2 KiB

View file

Before

Width:  |  Height:  |  Size: 3 KiB

After

Width:  |  Height:  |  Size: 3 KiB

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB