Merge "Adjust the background text image width to reduce its size"
This commit is contained in:
commit
4ef9cb27eb
2 changed files with 82 additions and 27 deletions
|
@ -101,7 +101,7 @@ TEST_P(ResourcesTest, ValidateLocale) {
|
||||||
EXPECT_LT(0, len) << "Locale string should be non-empty.";
|
EXPECT_LT(0, len) << "Locale string should be non-empty.";
|
||||||
EXPECT_NE(0, row[5]) << "Locale string is missing.";
|
EXPECT_NE(0, row[5]) << "Locale string is missing.";
|
||||||
|
|
||||||
ASSERT_GT(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
|
ASSERT_GE(png_->height(), y + 1 + h) << "Locale: " << kLocale << " is not found in the file.";
|
||||||
char* loc = reinterpret_cast<char*>(&row[5]);
|
char* loc = reinterpret_cast<char*>(&row[5]);
|
||||||
if (matches_locale(loc, kLocale.c_str())) {
|
if (matches_locale(loc, kLocale.c_str())) {
|
||||||
EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
|
EXPECT_TRUE(android::base::StartsWith(loc, kLocale));
|
||||||
|
|
|
@ -60,8 +60,9 @@ public class ImageGenerator {
|
||||||
// This is the canvas we used to draw texts.
|
// This is the canvas we used to draw texts.
|
||||||
private BufferedImage mBufferedImage;
|
private BufferedImage mBufferedImage;
|
||||||
|
|
||||||
// The width in pixels of our image. Once set, its value won't change.
|
// The width in pixels of our image. The value will be adjusted once when we calculate the
|
||||||
private final int mImageWidth;
|
// maximum width to fit the wrapped text strings.
|
||||||
|
private int mImageWidth;
|
||||||
|
|
||||||
// The current height in pixels of our image. We will adjust the value when drawing more texts.
|
// The current height in pixels of our image. We will adjust the value when drawing more texts.
|
||||||
private int mImageHeight;
|
private int mImageHeight;
|
||||||
|
@ -79,6 +80,9 @@ public class ImageGenerator {
|
||||||
// The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
|
// The directory that contains all the needed font files (e.g. ttf, otf, ttc files).
|
||||||
private final String mFontDirPath;
|
private final String mFontDirPath;
|
||||||
|
|
||||||
|
// Align the text in the center of the image.
|
||||||
|
private final boolean mCenterAlignment;
|
||||||
|
|
||||||
// An explicit map from language to the font name to use.
|
// An explicit map from language to the font name to use.
|
||||||
// The map is extracted from frameworks/base/data/fonts/fonts.xml.
|
// The map is extracted from frameworks/base/data/fonts/fonts.xml.
|
||||||
// And the language-subtag-registry is found in:
|
// And the language-subtag-registry is found in:
|
||||||
|
@ -130,7 +134,9 @@ public class ImageGenerator {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Languages that breaks on arbitrary characters.
|
// Languages that breaks on arbitrary characters.
|
||||||
// TODO(xunchang) switch to icu library if possible.
|
// TODO(xunchang) switch to icu library if possible. For example, for Thai and Khmer, there is
|
||||||
|
// no space between words; and word breaking is based on grammatical analysis and on word
|
||||||
|
// matching in dictionaries.
|
||||||
private static final Set<String> LOGOGRAM_LANGUAGE =
|
private static final Set<String> LOGOGRAM_LANGUAGE =
|
||||||
new HashSet<String>() {
|
new HashSet<String>() {
|
||||||
{
|
{
|
||||||
|
@ -138,6 +144,7 @@ public class ImageGenerator {
|
||||||
add("km"); // Khmer
|
add("km"); // Khmer
|
||||||
add("ko"); // Korean
|
add("ko"); // Korean
|
||||||
add("lo"); // Lao
|
add("lo"); // Lao
|
||||||
|
add("th"); // Thai
|
||||||
add("zh"); // Chinese
|
add("zh"); // Chinese
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -154,8 +161,13 @@ public class ImageGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Initailizes the fields of the image image. */
|
/** Initailizes the fields of the image image. */
|
||||||
public ImageGenerator(int imageWidth, String textName, float fontSize, String fontDirPath) {
|
public ImageGenerator(
|
||||||
mImageWidth = imageWidth;
|
int initialImageWidth,
|
||||||
|
String textName,
|
||||||
|
float fontSize,
|
||||||
|
String fontDirPath,
|
||||||
|
boolean centerAlignment) {
|
||||||
|
mImageWidth = initialImageWidth;
|
||||||
mImageHeight = INITIAL_HEIGHT;
|
mImageHeight = INITIAL_HEIGHT;
|
||||||
mVerticalOffset = 0;
|
mVerticalOffset = 0;
|
||||||
|
|
||||||
|
@ -165,6 +177,8 @@ public class ImageGenerator {
|
||||||
mTextName = textName;
|
mTextName = textName;
|
||||||
mFontSize = fontSize;
|
mFontSize = fontSize;
|
||||||
mFontDirPath = fontDirPath;
|
mFontDirPath = fontDirPath;
|
||||||
|
|
||||||
|
mCenterAlignment = centerAlignment;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -299,8 +313,6 @@ public class ImageGenerator {
|
||||||
List<String> wrappedText = new ArrayList<>();
|
List<String> wrappedText = new ArrayList<>();
|
||||||
StringTokenizer st = new StringTokenizer(text, " \n");
|
StringTokenizer st = new StringTokenizer(text, " \n");
|
||||||
|
|
||||||
// TODO(xunchang). We assume that all words can fit on the screen. Raise an
|
|
||||||
// IllegalStateException if the word is wider than the image width.
|
|
||||||
StringBuilder line = new StringBuilder();
|
StringBuilder line = new StringBuilder();
|
||||||
while (st.hasMoreTokens()) {
|
while (st.hasMoreTokens()) {
|
||||||
String token = st.nextToken();
|
String token = st.nextToken();
|
||||||
|
@ -373,6 +385,43 @@ public class ImageGenerator {
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns Graphics2D object that uses the given locale. */
|
||||||
|
private Graphics2D createGraphics(Locale locale) throws IOException, FontFormatException {
|
||||||
|
Graphics2D graphics = mBufferedImage.createGraphics();
|
||||||
|
graphics.setColor(Color.WHITE);
|
||||||
|
graphics.setRenderingHint(
|
||||||
|
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
|
||||||
|
graphics.setFont(loadFontsByLocale(locale.getLanguage()));
|
||||||
|
|
||||||
|
return graphics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the maximum screen width needed to fit the given text after wrapping. */
|
||||||
|
private int measureTextWidth(String text, Locale locale)
|
||||||
|
throws IOException, FontFormatException {
|
||||||
|
Graphics2D graphics = createGraphics(locale);
|
||||||
|
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||||
|
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
|
||||||
|
|
||||||
|
int textWidth = 0;
|
||||||
|
for (String line : wrappedText) {
|
||||||
|
textWidth = Math.max(textWidth, fontMetrics.stringWidth(line));
|
||||||
|
}
|
||||||
|
|
||||||
|
// This may happen if one single word is larger than the image width.
|
||||||
|
if (textWidth > mImageWidth) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Wrapped text width "
|
||||||
|
+ textWidth
|
||||||
|
+ " is larger than image width "
|
||||||
|
+ mImageWidth
|
||||||
|
+ " for locale: "
|
||||||
|
+ locale);
|
||||||
|
}
|
||||||
|
|
||||||
|
return textWidth;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Draws the text string on the canvas for given locale.
|
* Draws the text string on the canvas for given locale.
|
||||||
*
|
*
|
||||||
|
@ -381,16 +430,11 @@ public class ImageGenerator {
|
||||||
* @throws IOException if we cannot find the corresponding font file for the given locale.
|
* @throws IOException if we cannot find the corresponding font file for the given locale.
|
||||||
* @throws FontFormatException if we failed to load the font file for the given locale.
|
* @throws FontFormatException if we failed to load the font file for the given locale.
|
||||||
*/
|
*/
|
||||||
private void drawText(String text, Locale locale, String languageTag, boolean centralAlignment)
|
private void drawText(String text, Locale locale, String languageTag)
|
||||||
throws IOException, FontFormatException {
|
throws IOException, FontFormatException {
|
||||||
Graphics2D graphics = mBufferedImage.createGraphics();
|
|
||||||
graphics.setColor(Color.WHITE);
|
|
||||||
graphics.setRenderingHint(
|
|
||||||
RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_GASP);
|
|
||||||
graphics.setFont(loadFontsByLocale(locale.getLanguage()));
|
|
||||||
|
|
||||||
System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
|
System.out.println("Encoding \"" + locale + "\" as \"" + languageTag + "\": " + text);
|
||||||
|
|
||||||
|
Graphics2D graphics = createGraphics(locale);
|
||||||
FontMetrics fontMetrics = graphics.getFontMetrics();
|
FontMetrics fontMetrics = graphics.getFontMetrics();
|
||||||
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
|
List<String> wrappedText = wrapText(text, fontMetrics, locale.getLanguage());
|
||||||
|
|
||||||
|
@ -402,7 +446,7 @@ public class ImageGenerator {
|
||||||
int lineHeight = fontMetrics.getHeight();
|
int lineHeight = fontMetrics.getHeight();
|
||||||
// Doubles the height of the image if we are short of space.
|
// Doubles the height of the image if we are short of space.
|
||||||
if (mVerticalOffset + lineHeight >= mImageHeight) {
|
if (mVerticalOffset + lineHeight >= mImageHeight) {
|
||||||
resizeHeight(mImageHeight * 2);
|
resize(mImageWidth, mImageHeight * 2);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draws the text at mVerticalOffset and increments the offset with line space.
|
// Draws the text at mVerticalOffset and increments the offset with line space.
|
||||||
|
@ -410,7 +454,7 @@ public class ImageGenerator {
|
||||||
|
|
||||||
// Draws from right if it's an RTL language.
|
// Draws from right if it's an RTL language.
|
||||||
int x =
|
int x =
|
||||||
centralAlignment
|
mCenterAlignment
|
||||||
? (mImageWidth - fontMetrics.stringWidth(line)) / 2
|
? (mImageWidth - fontMetrics.stringWidth(line)) / 2
|
||||||
: RTL_LANGUAGE.contains(languageTag)
|
: RTL_LANGUAGE.contains(languageTag)
|
||||||
? mImageWidth - fontMetrics.stringWidth(line)
|
? mImageWidth - fontMetrics.stringWidth(line)
|
||||||
|
@ -431,18 +475,19 @@ public class ImageGenerator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Redraws the image with the new height.
|
* Redraws the image with the new width and new height.
|
||||||
*
|
*
|
||||||
|
* @param width the new width of the image in pixels.
|
||||||
* @param height the new height of the image in pixels.
|
* @param height the new height of the image in pixels.
|
||||||
*/
|
*/
|
||||||
private void resizeHeight(int height) {
|
private void resize(int width, int height) {
|
||||||
BufferedImage resizedImage =
|
BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_BYTE_GRAY);
|
||||||
new BufferedImage(mImageWidth, height, BufferedImage.TYPE_BYTE_GRAY);
|
|
||||||
Graphics2D graphic = resizedImage.createGraphics();
|
Graphics2D graphic = resizedImage.createGraphics();
|
||||||
graphic.drawImage(mBufferedImage, 0, 0, null);
|
graphic.drawImage(mBufferedImage, 0, 0, null);
|
||||||
graphic.dispose();
|
graphic.dispose();
|
||||||
|
|
||||||
mBufferedImage = resizedImage;
|
mBufferedImage = resizedImage;
|
||||||
|
mImageWidth = width;
|
||||||
mImageHeight = height;
|
mImageHeight = height;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -458,11 +503,16 @@ public class ImageGenerator {
|
||||||
public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
|
public void generateImage(Map<Locale, String> localizedTextMap, String outputPath)
|
||||||
throws FontFormatException, IOException {
|
throws FontFormatException, IOException {
|
||||||
Map<String, Integer> languageCount = new TreeMap<>();
|
Map<String, Integer> languageCount = new TreeMap<>();
|
||||||
|
int textWidth = 0;
|
||||||
for (Locale locale : localizedTextMap.keySet()) {
|
for (Locale locale : localizedTextMap.keySet()) {
|
||||||
String language = locale.getLanguage();
|
String language = locale.getLanguage();
|
||||||
languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
|
languageCount.put(language, languageCount.getOrDefault(language, 0) + 1);
|
||||||
|
textWidth = Math.max(textWidth, measureTextWidth(localizedTextMap.get(locale), locale));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Removes the black margins to reduce the size of the image.
|
||||||
|
resize(textWidth, mImageHeight);
|
||||||
|
|
||||||
for (Locale locale : localizedTextMap.keySet()) {
|
for (Locale locale : localizedTextMap.keySet()) {
|
||||||
Integer count = languageCount.get(locale.getLanguage());
|
Integer count = languageCount.get(locale.getLanguage());
|
||||||
// Recovery expects en-US instead of en_US.
|
// Recovery expects en-US instead of en_US.
|
||||||
|
@ -475,12 +525,10 @@ public class ImageGenerator {
|
||||||
languageCount.put(locale.getLanguage(), count - 1);
|
languageCount.put(locale.getLanguage(), count - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
drawText(localizedTextMap.get(locale), locale, languageTag, false);
|
drawText(localizedTextMap.get(locale), locale, languageTag);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(xunchang) adjust the width to save some space if all texts are smaller than
|
resize(mImageWidth, mVerticalOffset);
|
||||||
// imageWidth.
|
|
||||||
resizeHeight(mVerticalOffset);
|
|
||||||
ImageIO.write(mBufferedImage, "png", new File(outputPath));
|
ImageIO.write(mBufferedImage, "png", new File(outputPath));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -528,11 +576,17 @@ public class ImageGenerator {
|
||||||
|
|
||||||
options.addOption(
|
options.addOption(
|
||||||
OptionBuilder.withLongOpt("output_file")
|
OptionBuilder.withLongOpt("output_file")
|
||||||
.withDescription("Path to the generated image")
|
.withDescription("Path to the generated image.")
|
||||||
.hasArgs(1)
|
.hasArgs(1)
|
||||||
.isRequired()
|
.isRequired()
|
||||||
.create());
|
.create());
|
||||||
|
|
||||||
|
options.addOption(
|
||||||
|
OptionBuilder.withLongOpt("center_alignment")
|
||||||
|
.withDescription("Align the text in the center of the screen.")
|
||||||
|
.hasArg(false)
|
||||||
|
.create());
|
||||||
|
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +611,8 @@ public class ImageGenerator {
|
||||||
imageWidth,
|
imageWidth,
|
||||||
cmd.getOptionValue("text_name"),
|
cmd.getOptionValue("text_name"),
|
||||||
DEFAULT_FONT_SIZE,
|
DEFAULT_FONT_SIZE,
|
||||||
cmd.getOptionValue("font_dir"));
|
cmd.getOptionValue("font_dir"),
|
||||||
|
cmd.hasOption("center_alignment"));
|
||||||
|
|
||||||
Map<Locale, String> localizedStringMap =
|
Map<Locale, String> localizedStringMap =
|
||||||
imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
|
imageGenerator.readLocalizedStringFromXmls(cmd.getOptionValue("resource_dir"));
|
||||||
|
|
Loading…
Reference in a new issue