Skip to content

Commit

Permalink
Merge pull request #17854 from hrydgard/color-emoji-android
Browse files Browse the repository at this point in the history
Implement color emoji support for Android
  • Loading branch information
hrydgard authored Aug 6, 2023
2 parents 70622e0 + 63cfe28 commit 83dbc60
Show file tree
Hide file tree
Showing 9 changed files with 63 additions and 14 deletions.
17 changes: 17 additions & 0 deletions Common/Data/Encoding/Utf8.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,17 @@ int u8_is_locale_utf8(const char *locale)
return 0;
}

bool AnyEmojiInString(const char *s, size_t byteCount) {
int i = 0;
while (i < byteCount) {
uint32_t c = u8_nextchar(s, &i);
if (CodepointIsProbablyEmoji(c)) {
return true;
}
}
return false;
}

int UTF8StringNonASCIICount(const char *utf8string) {
UTF8 utf(utf8string);
int count = 0;
Expand Down Expand Up @@ -558,6 +569,12 @@ std::u16string ConvertUTF8ToUCS2(const std::string &source) {
return dst;
}

std::string CodepointToUTF8(uint32_t codePoint) {
char temp[16]{};
UTF8::encode(temp, codePoint);
return std::string(temp);
}

#ifndef _WIN32

// Replacements for the Win32 wstring functions. Not to be used from emulation code!
Expand Down
10 changes: 10 additions & 0 deletions Common/Data/Encoding/Utf8.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,14 @@ int u8_strlen(const char *s);
void u8_inc(const char *s, int *i);
void u8_dec(const char *s, int *i);

// ranges grabbed from https://stackoverflow.com/a/62898106, ignoring the two bogus ranges.
// there's probably more. Doesn't need to be perfect.
inline bool CodepointIsProbablyEmoji(uint32_t c) {
return (c >= 127744 && c <= 129782) || (c >= 126980 && c <= 127569);
}

bool AnyEmojiInString(const char *s, size_t byteCount);

class UTF8 {
public:
static const uint32_t INVALID = (uint32_t)-1;
Expand Down Expand Up @@ -89,6 +97,8 @@ bool UTF8StringHasNonASCII(const char *utf8string);
// Removes overlong encodings and similar.
std::string SanitizeUTF8(const std::string &utf8string);

std::string CodepointToUTF8(uint32_t codePoint);


// UTF8 to Win32 UTF-16
// Should be used when calling Win32 api calls
Expand Down
1 change: 1 addition & 0 deletions Common/GPU/Vulkan/thin3d_vulkan.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -897,6 +897,7 @@ VKContext::VKContext(VulkanContext *vulkan, bool useRenderThread)
caps_.logicOpSupported = vulkan->GetDeviceFeatures().enabled.standard.logicOp != 0;
caps_.multiViewSupported = vulkan->GetDeviceFeatures().enabled.multiview.multiview != 0;
caps_.sampleRateShadingSupported = vulkan->GetDeviceFeatures().enabled.standard.sampleRateShading != 0;
caps_.textureSwizzleSupported = true;

const auto &limits = vulkan->GetPhysicalDeviceProperties().properties.limits;

Expand Down
1 change: 1 addition & 0 deletions Common/GPU/thin3d.h
Original file line number Diff line number Diff line change
Expand Up @@ -604,6 +604,7 @@ struct DeviceCaps {
bool isTilingGPU; // This means that it benefits from correct store-ops, msaa without backing memory, etc.
bool sampleRateShadingSupported;
bool setMaxFrameLatencySupported;
bool textureSwizzleSupported;

bool verySlowShaderCompiler;

Expand Down
36 changes: 26 additions & 10 deletions Common/Render/Text/draw_text_android.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,10 @@ TextDrawerAndroid::TextDrawerAndroid(Draw::DrawContext *draw) : TextDrawer(draw)
}
dpiScale_ = CalculateDPIScale();

// Pick between the two supported formats, of which at least one is supported on each platform. Prefer R8 (but only if swizzle is supported)
use4444Format_ = (draw->GetDataFormatSupport(Draw::DataFormat::R4G4B4A4_UNORM_PACK16) & Draw::FMT_TEXTURE) != 0;

if ((draw->GetDataFormatSupport(Draw::DataFormat::R8_UNORM) & Draw::FMT_TEXTURE) != 0 && draw->GetDeviceCaps().textureSwizzleSupported)
use4444Format_ = false;
INFO_LOG(G3D, "Initializing TextDrawerAndroid with DPI scale %f, use4444=%d", dpiScale_, (int)use4444Format_);
}

Expand Down Expand Up @@ -206,20 +208,30 @@ void TextDrawerAndroid::DrawStringBitmap(std::vector<uint8_t> &bitmapData, TextS
if (texFormat == Draw::DataFormat::B4G4R4A4_UNORM_PACK16 || texFormat == Draw::DataFormat::R4G4B4A4_UNORM_PACK16) {
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint16_t));
uint16_t *bitmapData16 = (uint16_t *)&bitmapData[0];
for (int x = 0; x < entry.bmWidth; x++) {
for (int y = 0; y < entry.bmHeight; y++) {
for (int y = 0; y < entry.bmHeight; y++) {
for (int x = 0; x < entry.bmWidth; x++) {
uint32_t v = jimage[imageWidth * y + x];
v = 0xFFF0 | ((v >> 12) & 0xF); // Just grab some bits from the green channel.
v = 0xFFF0 | ((v >> 28) & 0xF); // Grab the upper bits from the alpha channel, and put directly in the 16-bit alpha channel.
bitmapData16[entry.bmWidth * y + x] = (uint16_t)v;
}
}
} else if (texFormat == Draw::DataFormat::R8_UNORM) {
bitmapData.resize(entry.bmWidth * entry.bmHeight);
for (int x = 0; x < entry.bmWidth; x++) {
for (int y = 0; y < entry.bmHeight; y++) {
for (int y = 0; y < entry.bmHeight; y++) {
for (int x = 0; x < entry.bmWidth; x++) {
uint32_t v = jimage[imageWidth * y + x];
bitmapData[entry.bmWidth * y + x] = (uint8_t)(v >> 24);
}
}
} else if (texFormat == Draw::DataFormat::R8G8B8A8_UNORM) {
bitmapData.resize(entry.bmWidth * entry.bmHeight * sizeof(uint32_t));
uint32_t *bitmapData32 = (uint32_t *)&bitmapData[0];
for (int y = 0; y < entry.bmHeight; y++) {
for (int x = 0; x < entry.bmWidth; x++) {
uint32_t v = jimage[imageWidth * y + x];
v = (v >> 12) & 0xF; // Just grab some bits from the green channel.
bitmapData[entry.bmWidth * y + x] = (uint8_t)(v | (v << 4));
// Swap R and B, for some reason.
v = (v & 0xFF00FF00) | ((v >> 16) & 0xFF) | ((v << 16) & 0xFF0000);
bitmapData32[entry.bmWidth * y + x] = v;
}
}
} else {
Expand All @@ -238,6 +250,8 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x,
if (text.empty())
return;

bool emoji = AnyEmojiInString(text.c_str(), text.size());

CacheKey key{ std::string(str), fontHash_ };
target.Flush(true);

Expand All @@ -248,8 +262,10 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x,
entry = iter->second.get();
entry->lastUsedFrame = frameCount_;
} else {
// Actually, I don't know why we don't always use R8_UNORM..
DataFormat texFormat = use4444Format_ ? Draw::DataFormat::R4G4B4A4_UNORM_PACK16 : Draw::DataFormat::R8_UNORM;
if (emoji) {
texFormat = Draw::DataFormat::R8G8B8A8_UNORM;
}

entry = new TextStringEntry();

Expand All @@ -265,7 +281,7 @@ void TextDrawerAndroid::DrawString(DrawBuffer &target, const char *str, float x,
desc.depth = 1;
desc.mipLevels = 1;
desc.generateMips = false;
desc.swizzle = use4444Format_ ? Draw::TextureSwizzle::DEFAULT : Draw::TextureSwizzle::R8_AS_ALPHA,
desc.swizzle = texFormat == Draw::DataFormat::R8_UNORM ? Draw::TextureSwizzle::R8_AS_ALPHA : Draw::TextureSwizzle::DEFAULT,
desc.tag = "TextDrawer";
entry->texture = draw_->CreateTexture(desc);
cache_[key] = std::unique_ptr<TextStringEntry>(entry);
Expand Down
1 change: 0 additions & 1 deletion Common/Render/Text/draw_text_uwp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,6 @@ TextDrawerUWP::TextDrawerUWP(Draw::DrawContext *draw) : TextDrawer(draw), ctx_(n
);

m_d2dContext->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::White, 1.0f), &m_d2dWhiteBrush);

}

TextDrawerUWP::~TextDrawerUWP() {
Expand Down
4 changes: 3 additions & 1 deletion UI/DevScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#endif
#include "Common/File/AndroidStorage.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Net/HTTPClient.h"
#include "Common/UI/Context.h"
#include "Common/UI/View.h"
Expand Down Expand Up @@ -834,7 +835,8 @@ void SystemInfoScreen::CreateTabs() {

internals->Add(new ItemHeader(si->T("Notification tests")));
internals->Add(new Choice(si->T("Error")))->OnClick.Add([&](UI::EventParams &) {
g_OSD.Show(OSDType::MESSAGE_ERROR, "Error");
std::string str = "Error " + CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914);
g_OSD.Show(OSDType::MESSAGE_ERROR, str);
return UI::EVENT_DONE;
});
internals->Add(new Choice(si->T("Warning")))->OnClick.Add([&](UI::EventParams &) {
Expand Down
5 changes: 4 additions & 1 deletion UI/MiscScreens.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
#include "Common/File/VFS/VFS.h"

#include "Common/Data/Color/RGBAUtil.h"
#include "Common/Data/Encoding/Utf8.h"
#include "Common/Data/Text/I18n.h"
#include "Common/Data/Random/Rng.h"
#include "Common/TimeUtil.h"
Expand Down Expand Up @@ -798,7 +799,9 @@ void LogoScreen::render() {
// Draw the graphics API, except on UWP where it's always D3D11
std::string apiName = screenManager()->getDrawContext()->GetInfoString(InfoField::APINAME);
#ifdef _DEBUG
apiName += ", debug build";
apiName += ", debug build ";
// Add some emoji for testing.
apiName += CodepointToUTF8(0x1F41B) + CodepointToUTF8(0x1F41C) + CodepointToUTF8(0x1F914);
#endif
dc.DrawText(gr->T(apiName), bounds.centerX(), ppsspp_org_y + 50, textColor, ALIGN_CENTER);
#endif
Expand Down
2 changes: 1 addition & 1 deletion android/src/org/ppsspp/ppsspp/TextRenderer.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class TextRenderer {
p = new Paint(Paint.SUBPIXEL_TEXT_FLAG | Paint.ANTI_ALIAS_FLAG);
p.setColor(Color.WHITE);
bg = new Paint();
bg.setColor(Color.BLACK);
bg.setColor(0);
}

public static void init(Context ctx) {
Expand Down

0 comments on commit 83dbc60

Please sign in to comment.