diff options
7 files changed, 183 insertions, 52 deletions
diff --git a/pdf/framework/libs/pdfClient/image_object.cc b/pdf/framework/libs/pdfClient/image_object.cc index 61bb85c69..69dedb0bc 100644 --- a/pdf/framework/libs/pdfClient/image_object.cc +++ b/pdf/framework/libs/pdfClient/image_object.cc @@ -26,6 +26,23 @@ namespace pdfClient { +BitmapFormat GetBitmapFormat(int bitmap_format) { + switch (bitmap_format) { + case FPDFBitmap_BGR: { + return BitmapFormat::BGR; + } + case FPDFBitmap_BGRA: { + return BitmapFormat::BGRA; + } + case FPDFBitmap_BGRx: { + return BitmapFormat::BGRx; + } + default: { + return BitmapFormat::Unknown; + } + } +} + ImageObject::ImageObject() : PageObject(Type::Image) {} ScopedFPDFPageObject ImageObject::CreateFPDFInstance(FPDF_DOCUMENT document, FPDF_PAGE page) { @@ -61,31 +78,42 @@ bool ImageObject::UpdateFPDFInstance(FPDF_PAGEOBJECT image_object, FPDF_PAGE pag return false; } + // Set the updated dimensions. width_ = FPDFBitmap_GetWidth(bitmap_.get()); height_ = FPDFBitmap_GetHeight(bitmap_.get()); + // Set the updated bitmap format. + bitmap_format_ = GetBitmapFormat(FPDFBitmap_GetFormat(bitmap_.get())); + return true; } bool ImageObject::PopulateFromFPDFInstance(FPDF_PAGEOBJECT image_object, FPDF_PAGE page) { - // Get Bitmap + // Get bitmap. bitmap_ = ScopedFPDFBitmap(FPDFImageObj_GetBitmap(image_object)); if (bitmap_.get() == nullptr) { return false; } - // Get Matrix + // Get matrix. if (!GetPageToDeviceMatrix(image_object, page)) { return false; } + // Get dimensions. width_ = FPDFBitmap_GetWidth(bitmap_.get()); height_ = FPDFBitmap_GetHeight(bitmap_.get()); + // Get bitmap format. + bitmap_format_ = GetBitmapFormat(FPDFBitmap_GetFormat(bitmap_.get())); + if (bitmap_format_ == BitmapFormat::Unknown) { + LOGE("Bitmap format unknown"); + return false; + } return true; } -void* ImageObject::GetBitmapReadableBuffer() const { +void* ImageObject::GetBitmapBuffer() const { return FPDFBitmap_GetBuffer(bitmap_.get()); } diff --git a/pdf/framework/libs/pdfClient/image_object.h b/pdf/framework/libs/pdfClient/image_object.h index 2e3aa9541..e3563ba8e 100644 --- a/pdf/framework/libs/pdfClient/image_object.h +++ b/pdf/framework/libs/pdfClient/image_object.h @@ -27,6 +27,13 @@ typedef unsigned int uint; namespace pdfClient { +enum class BitmapFormat { + Unknown = -1, + BGR, + BGRA, + BGRx, +}; + class ImageObject : public PageObject { public: ImageObject(); @@ -35,12 +42,13 @@ class ImageObject : public PageObject { bool UpdateFPDFInstance(FPDF_PAGEOBJECT image_object, FPDF_PAGE page) override; bool PopulateFromFPDFInstance(FPDF_PAGEOBJECT image_object, FPDF_PAGE page) override; - void* GetBitmapReadableBuffer() const; + void* GetBitmapBuffer() const; ~ImageObject(); - int width_ = 0; - int height_ = 0; + size_t width_ = 0; + size_t height_ = 0; + BitmapFormat bitmap_format_ = BitmapFormat::Unknown; ScopedFPDFBitmap bitmap_; }; diff --git a/pdf/framework/libs/pdfClient/jni_conversion.cc b/pdf/framework/libs/pdfClient/jni_conversion.cc index c132b6698..46d5b2d9e 100644 --- a/pdf/framework/libs/pdfClient/jni_conversion.cc +++ b/pdf/framework/libs/pdfClient/jni_conversion.cc @@ -25,6 +25,7 @@ #include "text_object.h" using pdfClient::Annotation; +using pdfClient::BitmapFormat; using pdfClient::Color; using pdfClient::Document; using pdfClient::Font; @@ -450,7 +451,41 @@ jobject ToJavaGotoLinks(JNIEnv* env, const vector<GotoLink>& links) { return ToJavaList(env, links, &ToJavaGotoLink); } -jobject ToJavaBitmap(JNIEnv* env, void* buffer, int width, int height) { +void ConvertBgrToRgba(uint32_t* rgba_pixel_array, uint8_t* bgr_pixel_array, size_t rgba_stride, + size_t bgr_stride, size_t width, size_t height) { + for (size_t y = 0; y < height; y++) { + uint32_t* rgba_row_ptr = rgba_pixel_array + y * (rgba_stride / 4); + uint8_t* bgr_row_ptr = bgr_pixel_array + y * (bgr_stride); + for (size_t x = 0; x < width; x++) { + // Extract BGR components stored. + uint8_t blue = bgr_row_ptr[x * 3]; + uint8_t green = bgr_row_ptr[x * 3 + 1]; + uint8_t red = bgr_row_ptr[x * 3 + 2]; + // Storing java bitmap components RGBA in little-endian. + rgba_row_ptr[x] = (0xFF << 24) | (blue << 16) | (green << 8) | red; + } + } +} + +void ConvertBgraToRgba(uint32_t* rgba_pixel_array, uint8_t* bgra_pixel_array, size_t rgba_stride, + size_t bgra_stride, size_t width, size_t height, bool ignore_alpha) { + for (size_t y = 0; y < height; y++) { + uint32_t* rgba_row_ptr = rgba_pixel_array + y * (rgba_stride / 4); + uint8_t* bgra_row_ptr = bgra_pixel_array + y * (bgra_stride); + for (size_t x = 0; x < width; x++) { + // Extract BGR components and determine alpha based on ignore_alpha flag. + uint8_t blue = bgra_row_ptr[x * 4]; + uint8_t green = bgra_row_ptr[x * 4 + 1]; + uint8_t red = bgra_row_ptr[x * 4 + 2]; + uint8_t alpha = ignore_alpha ? 0xFF : bgra_row_ptr[x * 4 + 3]; + // Storing java bitmap components RGBA in little-endian. + rgba_row_ptr[x] = (alpha << 24) | (blue << 16) | (green << 8) | red; + } + } +} + +jobject ToJavaBitmap(JNIEnv* env, void* buffer, BitmapFormat bitmap_format, size_t width, + size_t height, size_t native_stride) { // Find Java Bitmap class static jclass bitmap_class = GetPermClassRef(env, kBitmap); @@ -469,16 +504,41 @@ jobject ToJavaBitmap(JNIEnv* env, void* buffer, int width, int height) { jobject java_bitmap = env->CallStaticObjectMethod(bitmap_class, create_bitmap, width, height, argb8888); - // Lock the Bitmap pixels for copying + // Copy the buffer data into java bitmap. + AndroidBitmapInfo bitmap_info; + AndroidBitmap_getInfo(env, java_bitmap, &bitmap_info); + size_t java_stride = bitmap_info.stride; + void* bitmap_pixels; if (AndroidBitmap_lockPixels(env, java_bitmap, &bitmap_pixels) < 0) { return NULL; } - // Copy the buffer data into java Bitmap. - std::memcpy(bitmap_pixels, buffer, width * height); // 4 bytes per pixel (ARGB_8888) + uint32_t* java_pixel_array = static_cast<uint32_t*>(bitmap_pixels); + uint8_t* native_pixel_array = static_cast<uint8_t*>(buffer); + switch (bitmap_format) { + case BitmapFormat::BGR: { + ConvertBgrToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride, + width, height); + break; + } + case BitmapFormat::BGRA: { + ConvertBgraToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride, + width, height, false); + break; + } + case BitmapFormat::BGRx: { + ConvertBgraToRgba(java_pixel_array, native_pixel_array, java_stride, native_stride, + width, height, true); + break; + } + default: { + LOGE("Bitmap format unknown!"); + AndroidBitmap_unlockPixels(env, java_bitmap); + return NULL; + } + } - // Unlock the Bitmap pixels AndroidBitmap_unlockPixels(env, java_bitmap); return java_bitmap; @@ -682,11 +742,17 @@ jobject ToJavaPdfImageObject(JNIEnv* env, const ImageObject* image_object) { static jmethodID init_image = env->GetMethodID(image_object_class, "<init>", funcsig("V", kBitmap).c_str()); - // Get Bitmap readable buffer from ImageObject Data. - void* buffer = image_object->GetBitmapReadableBuffer(); - // Create Java Bitmap from Native Bitmap Buffer. - jobject java_bitmap = ToJavaBitmap(env, buffer, image_object->width_, image_object->height_); + void* buffer = image_object->GetBitmapBuffer(); + BitmapFormat bitmap_format = image_object->bitmap_format_; + size_t width = image_object->width_; + size_t height = image_object->height_; + int stride = FPDFBitmap_GetStride(image_object->bitmap_.get()); + jobject java_bitmap = ToJavaBitmap(env, buffer, bitmap_format, width, height, stride); + if (java_bitmap == NULL) { + LOGE("To java bitmap conversion failed!"); + return NULL; + } // Create Java PdfImageObject Instance. jobject java_image_object = env->NewObject(image_object_class, init_image, java_bitmap); @@ -934,6 +1000,23 @@ std::unique_ptr<PathObject> ToNativePathObject(JNIEnv* env, jobject java_path_ob return path_object; } +void CopyRgbaToBgra(uint8_t* rgba_pixel_array, size_t rgba_stride, uint32_t* bgra_pixel_array, + size_t bgra_stride, size_t width, size_t height) { + for (size_t y = 0; y < height; y++) { + uint8_t* rgba_row_ptr = rgba_pixel_array + y * rgba_stride; + uint32_t* bgra_row_ptr = bgra_pixel_array + y * (bgra_stride / 4); + for (size_t x = 0; x < width; x++) { + // Extract RGBA components stored. + uint8_t red = rgba_row_ptr[x * 4]; + uint8_t green = rgba_row_ptr[x * 4 + 1]; + uint8_t blue = rgba_row_ptr[x * 4 + 2]; + uint8_t alpha = rgba_row_ptr[x * 4 + 3]; + // Storing native bitmap components BGRA in little-endian. + bgra_row_ptr[x] = (alpha << 24) | (red << 16) | (green << 8) | blue; + } + } +} + std::unique_ptr<ImageObject> ToNativeImageObject(JNIEnv* env, jobject java_image_object) { // Create ImageObject Data Instance. auto image_object = std::make_unique<ImageObject>(); @@ -946,21 +1029,34 @@ std::unique_ptr<ImageObject> ToNativeImageObject(JNIEnv* env, jobject java_image env->GetMethodID(image_object_class, "getBitmap", funcsig(kBitmap).c_str()); jobject java_bitmap = env->CallObjectMethod(java_image_object, get_bitmap); - // Create an FPDF_BITMAP from the Android Bitmap. + // Get android bitmap info. + AndroidBitmapInfo bitmap_info; + AndroidBitmap_getInfo(env, java_bitmap, &bitmap_info); + if (bitmap_info.format != ANDROID_BITMAP_FORMAT_RGBA_8888) { + LOGE("Android bitmap is not in RGBA_8888 format"); + return nullptr; + } + size_t bitmap_width = bitmap_info.width; + size_t bitmap_height = bitmap_info.height; + size_t java_stride = bitmap_info.stride; + + // Create ImageObject data bitmap. + image_object->bitmap_ = ScopedFPDFBitmap(FPDFBitmap_Create(bitmap_width, bitmap_height, 1)); + size_t native_stride = FPDFBitmap_GetStride(image_object->bitmap_.get()); + + // Copy pixels from android bitmap. void* bitmap_pixels; if (AndroidBitmap_lockPixels(env, java_bitmap, &bitmap_pixels) < 0) { + LOGE("Android bitmap lock pixels failed!"); return nullptr; } - AndroidBitmapInfo bitmap_info; - AndroidBitmap_getInfo(env, java_bitmap, &bitmap_info); - const int stride = bitmap_info.width * 4; + uint8_t* java_pixel_array = static_cast<uint8_t*>(bitmap_pixels); + uint32_t* native_pixel_array = static_cast<uint32_t*>(image_object->GetBitmapBuffer()); - // Set ImageObject Data Bitmap - image_object->bitmap_ = ScopedFPDFBitmap(FPDFBitmap_CreateEx( - bitmap_info.width, bitmap_info.height, FPDFBitmap_BGRA, bitmap_pixels, stride)); + CopyRgbaToBgra(java_pixel_array, java_stride, native_pixel_array, native_stride, bitmap_width, + bitmap_height); - // Unlock the Android Bitmap AndroidBitmap_unlockPixels(env, java_bitmap); return image_object; diff --git a/pdf/framework/libs/pdfClient/jni_conversion.h b/pdf/framework/libs/pdfClient/jni_conversion.h index 2b679405b..30b784250 100644 --- a/pdf/framework/libs/pdfClient/jni_conversion.h +++ b/pdf/framework/libs/pdfClient/jni_conversion.h @@ -117,8 +117,6 @@ jobject ToJavaGotoLink(JNIEnv* env, const GotoLink link); jobject ToJavaGotoLinks(JNIEnv* env, const vector<GotoLink>& links); -jobject ToJavaBitmap(JNIEnv* env, void* buffer, int width, int height); - jobject ToJavaColor(JNIEnv* env, Color color); jfloatArray ToJavaFloatArray(JNIEnv* env, const float arr[], size_t length); diff --git a/tests/src/com/android/providers/media/backupandrestore/BackupExecutorTest.java b/tests/src/com/android/providers/media/backupandrestore/BackupExecutorTest.java index 70c7eb373..83a132c62 100644 --- a/tests/src/com/android/providers/media/backupandrestore/BackupExecutorTest.java +++ b/tests/src/com/android/providers/media/backupandrestore/BackupExecutorTest.java @@ -18,12 +18,15 @@ package com.android.providers.media.backupandrestore; import static com.android.providers.media.backupandrestore.BackupAndRestoreTestUtils.deSerialiseValueString; import static com.android.providers.media.backupandrestore.BackupAndRestoreUtils.BACKUP_COLUMNS; +import static com.android.providers.media.backupandrestore.BackupAndRestoreUtils.isBackupAndRestoreSupported; import static com.android.providers.media.scan.MediaScanner.REASON_UNKNOWN; import static com.android.providers.media.scan.MediaScannerTest.stage; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; +import static org.junit.Assume.assumeTrue; + import android.Manifest; import android.content.ContentResolver; import android.content.Context; @@ -32,9 +35,8 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.SystemClock; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.MediaStore; import androidx.test.InstrumentationRegistry; @@ -68,12 +70,12 @@ import java.util.Optional; import java.util.Set; @RunWith(AndroidJUnit4.class) -@RequiresFlagsEnabled(com.android.providers.media.flags.Flags.FLAG_ENABLE_BACKUP_AND_RESTORE) +@EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_BACKUP_AND_RESTORE) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public final class BackupExecutorTest { @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Set<File> mStagedFiles = new HashSet<>(); @@ -103,7 +105,7 @@ public final class BackupExecutorTest { mDownloadsDir = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); mLevelDbPath = - mIsolatedContext.getFilesDir().getAbsolutePath() + "/backup/external_primary/"; + mIsolatedContext.getFilesDir().getAbsolutePath() + "/backup/external_primary"; FileUtils.deleteContents(mDownloadsDir); } @@ -121,6 +123,7 @@ public final class BackupExecutorTest { @Test public void testBackup() throws Exception { + assumeTrue(isBackupAndRestoreSupported(mIsolatedContext)); try { // Add all files in Downloads directory File file = new File(mDownloadsDir, "a_" + SystemClock.elapsedRealtimeNanos() + ".jpg"); diff --git a/tests/src/com/android/providers/media/backupandrestore/MediaBackupAgentTest.java b/tests/src/com/android/providers/media/backupandrestore/MediaBackupAgentTest.java index 5f892f126..e072f35bc 100644 --- a/tests/src/com/android/providers/media/backupandrestore/MediaBackupAgentTest.java +++ b/tests/src/com/android/providers/media/backupandrestore/MediaBackupAgentTest.java @@ -18,6 +18,7 @@ package com.android.providers.media.backupandrestore; import static com.android.providers.media.backupandrestore.BackupAndRestoreTestUtils.deSerialiseValueString; import static com.android.providers.media.backupandrestore.BackupAndRestoreTestUtils.getSharedPreferenceValue; +import static com.android.providers.media.backupandrestore.BackupAndRestoreUtils.isBackupAndRestoreSupported; import static com.android.providers.media.scan.MediaScanner.REASON_UNKNOWN; import static com.android.providers.media.scan.MediaScannerTest.stage; @@ -25,6 +26,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; +import static org.junit.Assume.assumeTrue; import android.Manifest; import android.content.ContentResolver; @@ -99,7 +101,7 @@ public class MediaBackupAgentTest { mDownloadsDir = new File(Environment.getExternalStorageDirectory(), Environment.DIRECTORY_DOWNLOADS); mLevelDbPath = - mIsolatedContext.getFilesDir().getAbsolutePath() + "/backup/external_primary/"; + mIsolatedContext.getFilesDir().getAbsolutePath() + "/backup/external_primary"; FileUtils.deleteContents(mDownloadsDir); mMediaBackupAgent = new MediaBackupAgent(); @@ -108,6 +110,7 @@ public class MediaBackupAgentTest { @Test public void testCompleteFlow() throws Exception { + assumeTrue(isBackupAndRestoreSupported(mIsolatedContext)); //create new test file & stage it File file = new File(mDownloadsDir, "testImage_" + SystemClock.elapsedRealtimeNanos() + ".jpg"); diff --git a/tests/src/com/android/providers/media/backupandrestore/RestoreExecutorTest.java b/tests/src/com/android/providers/media/backupandrestore/RestoreExecutorTest.java index 9f0b41a21..cf333b205 100644 --- a/tests/src/com/android/providers/media/backupandrestore/RestoreExecutorTest.java +++ b/tests/src/com/android/providers/media/backupandrestore/RestoreExecutorTest.java @@ -18,11 +18,14 @@ package com.android.providers.media.backupandrestore; import static com.android.providers.media.backupandrestore.BackupAndRestoreTestUtils.createSerialisedValue; import static com.android.providers.media.backupandrestore.BackupAndRestoreUtils.RESTORE_COMPLETED; +import static com.android.providers.media.backupandrestore.BackupAndRestoreUtils.isBackupAndRestoreSupported; import static com.android.providers.media.scan.MediaScanner.REASON_UNKNOWN; import static com.android.providers.media.scan.MediaScannerTest.stage; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assume.assumeTrue; + import android.Manifest; import android.content.ContentResolver; import android.content.Context; @@ -32,9 +35,8 @@ import android.os.Build; import android.os.Bundle; import android.os.Environment; import android.os.SystemClock; -import android.platform.test.annotations.RequiresFlagsEnabled; -import android.platform.test.flag.junit.CheckFlagsRule; -import android.platform.test.flag.junit.DeviceFlagsValueProvider; +import android.platform.test.annotations.EnableFlags; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.MediaStore; import androidx.test.InstrumentationRegistry; @@ -58,18 +60,16 @@ import org.junit.runner.RunWith; import java.io.File; import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @RunWith(AndroidJUnit4.class) -@RequiresFlagsEnabled(com.android.providers.media.flags.Flags.FLAG_ENABLE_BACKUP_AND_RESTORE) +@EnableFlags(com.android.providers.media.flags.Flags.FLAG_ENABLE_BACKUP_AND_RESTORE) @SdkSuppress(minSdkVersion = Build.VERSION_CODES.S) public final class RestoreExecutorTest { @Rule - public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule(); + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private Context mIsolatedContext; @@ -105,8 +105,9 @@ public final class RestoreExecutorTest { @Test public void testMetadataRestoreForImageFile() throws Exception { + assumeTrue(isBackupAndRestoreSupported(mIsolatedContext)); String levelDbPath = - mIsolatedContext.getFilesDir().getAbsolutePath() + "/restore/external_primary/"; + mIsolatedContext.getFilesDir().getAbsolutePath() + "/restore/external_primary"; if (!new File(levelDbPath).exists()) { new File(levelDbPath).mkdirs(); } @@ -277,12 +278,10 @@ public final class RestoreExecutorTest { } } - private void seedAudioDataIntoLevelDb(File testAudioFile, LevelDBInstance levelDBInstance) - throws IOException { + private void seedAudioDataIntoLevelDb(File testAudioFile, LevelDBInstance levelDBInstance) { Map<String, String> values = new HashMap<>(); values.put(MediaStore.Files.FileColumns.OWNER_PACKAGE_NAME, "com.hello.audio"); - values.put(MediaStore.Files.FileColumns.SIZE, - String.valueOf(Files.size(Path.of(testAudioFile.getAbsolutePath())))); + values.put(MediaStore.Files.FileColumns.SIZE, String.valueOf(testAudioFile.length())); values.put(MediaStore.Files.FileColumns.TITLE, "MyAudio"); values.put(MediaStore.Audio.AudioColumns.TRACK, "Forever"); values.put(MediaStore.Files.FileColumns.DURATION, "120"); @@ -294,12 +293,10 @@ public final class RestoreExecutorTest { createSerialisedValue(values))).isSuccess()).isTrue(); } - private void seedVideoDataIntoLevelDb(File testVideoFile, LevelDBInstance levelDBInstance) - throws IOException { + private void seedVideoDataIntoLevelDb(File testVideoFile, LevelDBInstance levelDBInstance) { Map<String, String> values = new HashMap<>(); values.put(MediaStore.Files.FileColumns.OWNER_PACKAGE_NAME, "com.hello.video"); - values.put(MediaStore.Files.FileColumns.SIZE, - String.valueOf(Files.size(Path.of(testVideoFile.getAbsolutePath())))); + values.put(MediaStore.Files.FileColumns.SIZE, String.valueOf(testVideoFile.length())); values.put(MediaStore.Files.FileColumns.TITLE, "MyVideo"); values.put(MediaStore.Video.VideoColumns.COLOR_STANDARD, "1"); values.put(MediaStore.Video.VideoColumns.COLOR_RANGE, "5"); @@ -311,12 +308,10 @@ public final class RestoreExecutorTest { createSerialisedValue(values))).isSuccess()).isTrue(); } - private void seedImageDataIntoLevelDb(File testFile, LevelDBInstance levelDBInstance) - throws IOException { + private void seedImageDataIntoLevelDb(File testFile, LevelDBInstance levelDBInstance) { Map<String, String> values = new HashMap<>(); values.put(MediaStore.Files.FileColumns.OWNER_PACKAGE_NAME, "com.hello.image"); - values.put(MediaStore.Files.FileColumns.SIZE, - String.valueOf(Files.size(Path.of(testFile.getAbsolutePath())))); + values.put(MediaStore.Files.FileColumns.SIZE, String.valueOf(testFile.length())); values.put(MediaStore.Files.FileColumns.TITLE, "MyImage"); values.put(MediaStore.Files.FileColumns.HEIGHT, "1600"); values.put(MediaStore.Files.FileColumns.WIDTH, "3200"); |