Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement color emoji support for Android #17854

Merged
merged 2 commits into from
Aug 6, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I would just assume anything with characters outside the BMP (i.e. c > 0xFFFF) includes emoji. Unless someone is using Ancient Sumerian or Egyptian Hieroglyphics, that's probably the only reason most people will use non-BMP glyphs. I guess the other thing is advanced math symbols.

-[Unknown]

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I guess that's the safer way to go...

}

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);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean for this to stay? I mean, it's fine, just noting in case.

-[Unknown]

Copy link
Owner Author

@hrydgard hrydgard Aug 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, since it's debug build only :) and having them here is convenient for checking.

#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