mirror of
				https://codeberg.org/tom79/Fedilab.git
				synced 2025-10-20 11:20:16 +03:00 
			
		
		
		
	Fix issue #219 - Use focus point for image preview
This commit is contained in:
		
							parent
							
								
									383702bf6a
								
							
						
					
					
						commit
						0f513f00c4
					
				
					 3 changed files with 257 additions and 0 deletions
				
			
		|  | @ -43,7 +43,36 @@ public class Attachment implements Serializable { | ||||||
|     public long size; |     public long size; | ||||||
|     @SerializedName("local_path") |     @SerializedName("local_path") | ||||||
|     public String local_path; |     public String local_path; | ||||||
|  |     @SerializedName("meta") | ||||||
|  |     public Meta meta; | ||||||
| 
 | 
 | ||||||
|     public String peertubeHost = null; |     public String peertubeHost = null; | ||||||
|     public String peertubeId = null; |     public String peertubeId = null; | ||||||
|  | 
 | ||||||
|  |     public static class Meta implements Serializable { | ||||||
|  |         @SerializedName("focus") | ||||||
|  |         public Focus focus; | ||||||
|  |         @SerializedName("original") | ||||||
|  |         public MediaData original; | ||||||
|  |         @SerializedName("small") | ||||||
|  |         public MediaData small; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static class Focus implements Serializable { | ||||||
|  |         @SerializedName("x") | ||||||
|  |         public float x; | ||||||
|  |         @SerializedName("y") | ||||||
|  |         public float y; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     public static class MediaData implements Serializable { | ||||||
|  |         @SerializedName("width") | ||||||
|  |         public int width; | ||||||
|  |         @SerializedName("height") | ||||||
|  |         public int height; | ||||||
|  |         @SerializedName("size") | ||||||
|  |         public String size; | ||||||
|  |         @SerializedName("aspect") | ||||||
|  |         public float aspect; | ||||||
|  |     } | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										209
									
								
								app/src/main/java/app/fedilab/android/helper/GlideFocus.java
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										209
									
								
								app/src/main/java/app/fedilab/android/helper/GlideFocus.java
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,209 @@ | ||||||
|  | package app.fedilab.android.helper; | ||||||
|  | /* Copyright 2021 Thomas Schneider | ||||||
|  |  * | ||||||
|  |  * This file is a part of Fedilab | ||||||
|  |  * | ||||||
|  |  * This program is free software; you can redistribute it and/or modify it under the terms of the | ||||||
|  |  * GNU General Public License as published by the Free Software Foundation; either version 3 of the | ||||||
|  |  * License, or (at your option) any later version. | ||||||
|  |  * | ||||||
|  |  * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even | ||||||
|  |  * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General | ||||||
|  |  * Public License for more details. | ||||||
|  |  * | ||||||
|  |  * You should have received a copy of the GNU General Public License along with Fedilab; if not, | ||||||
|  |  * see <http://www.gnu.org/licenses>. */ | ||||||
|  | 
 | ||||||
|  | import static com.bumptech.glide.load.resource.bitmap.TransformationUtils.PAINT_FLAGS; | ||||||
|  | 
 | ||||||
|  | import android.content.Context; | ||||||
|  | import android.graphics.Bitmap; | ||||||
|  | import android.graphics.Canvas; | ||||||
|  | import android.graphics.Matrix; | ||||||
|  | import android.graphics.Paint; | ||||||
|  | import android.os.Build; | ||||||
|  | 
 | ||||||
|  | import androidx.annotation.NonNull; | ||||||
|  | 
 | ||||||
|  | import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool; | ||||||
|  | import com.bumptech.glide.load.resource.bitmap.TransformationUtils; | ||||||
|  | import com.bumptech.glide.util.Synthetic; | ||||||
|  | 
 | ||||||
|  | import java.security.MessageDigest; | ||||||
|  | import java.util.Arrays; | ||||||
|  | import java.util.HashSet; | ||||||
|  | import java.util.Set; | ||||||
|  | import java.util.concurrent.TimeUnit; | ||||||
|  | import java.util.concurrent.locks.Condition; | ||||||
|  | import java.util.concurrent.locks.Lock; | ||||||
|  | import java.util.concurrent.locks.ReentrantLock; | ||||||
|  | 
 | ||||||
|  | import jp.wasabeef.glide.transformations.BitmapTransformation; | ||||||
|  | 
 | ||||||
|  | public class GlideFocus extends BitmapTransformation { | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     private static final int VERSION = 1; | ||||||
|  |     private static final String ID = "app.fedilab.android.GlideFocus." + VERSION; | ||||||
|  |     private static final Set<String> MODELS_REQUIRING_BITMAP_LOCK = | ||||||
|  |             new HashSet<>( | ||||||
|  |                     Arrays.asList( | ||||||
|  |                             // Moto X gen 2 | ||||||
|  |                             "XT1085", | ||||||
|  |                             "XT1092", | ||||||
|  |                             "XT1093", | ||||||
|  |                             "XT1094", | ||||||
|  |                             "XT1095", | ||||||
|  |                             "XT1096", | ||||||
|  |                             "XT1097", | ||||||
|  |                             "XT1098", | ||||||
|  |                             // Moto G gen 1 | ||||||
|  |                             "XT1031", | ||||||
|  |                             "XT1028", | ||||||
|  |                             "XT937C", | ||||||
|  |                             "XT1032", | ||||||
|  |                             "XT1008", | ||||||
|  |                             "XT1033", | ||||||
|  |                             "XT1035", | ||||||
|  |                             "XT1034", | ||||||
|  |                             "XT939G", | ||||||
|  |                             "XT1039", | ||||||
|  |                             "XT1040", | ||||||
|  |                             "XT1042", | ||||||
|  |                             "XT1045", | ||||||
|  |                             // Moto G gen 2 | ||||||
|  |                             "XT1063", | ||||||
|  |                             "XT1064", | ||||||
|  |                             "XT1068", | ||||||
|  |                             "XT1069", | ||||||
|  |                             "XT1072", | ||||||
|  |                             "XT1077", | ||||||
|  |                             "XT1078", | ||||||
|  |                             "XT1079")); | ||||||
|  |     private static final Lock BITMAP_DRAWABLE_LOCK = | ||||||
|  |             MODELS_REQUIRING_BITMAP_LOCK.contains(Build.MODEL) ? new ReentrantLock() : new NoLock(); | ||||||
|  |     private static final Paint DEFAULT_PAINT = new Paint(PAINT_FLAGS); | ||||||
|  |     private final float focalX; | ||||||
|  |     private final float focalY; | ||||||
|  | 
 | ||||||
|  |     public GlideFocus(float focalX, float focalY) { | ||||||
|  |         this.focalX = focalX; | ||||||
|  |         this.focalY = focalY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static void applyMatrix( | ||||||
|  |             @NonNull Bitmap inBitmap, @NonNull Bitmap targetBitmap, Matrix matrix) { | ||||||
|  |         BITMAP_DRAWABLE_LOCK.lock(); | ||||||
|  |         try { | ||||||
|  |             Canvas canvas = new Canvas(targetBitmap); | ||||||
|  |             canvas.drawBitmap(inBitmap, matrix, DEFAULT_PAINT); | ||||||
|  |             clear(canvas); | ||||||
|  |         } finally { | ||||||
|  |             BITMAP_DRAWABLE_LOCK.unlock(); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @NonNull | ||||||
|  |     private static Bitmap.Config getNonNullConfig(@NonNull Bitmap bitmap) { | ||||||
|  |         return bitmap.getConfig() != null ? bitmap.getConfig() : Bitmap.Config.ARGB_8888; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     // Avoids warnings in M+. | ||||||
|  |     private static void clear(Canvas canvas) { | ||||||
|  |         canvas.setBitmap(null); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     protected Bitmap transform(@NonNull Context context, @NonNull BitmapPool pool, | ||||||
|  |                                @NonNull Bitmap inBitmap, int width, int height) { | ||||||
|  | 
 | ||||||
|  |         if (inBitmap.getWidth() == width && inBitmap.getHeight() == height) { | ||||||
|  |             return inBitmap; | ||||||
|  |         } | ||||||
|  |         // From ImageView/Bitmap.createScaledBitmap. | ||||||
|  |         final float scale; | ||||||
|  |         final float dx; | ||||||
|  |         final float dy; | ||||||
|  |         Matrix m = new Matrix(); | ||||||
|  |         if (inBitmap.getWidth() * height > width * inBitmap.getHeight()) { | ||||||
|  |             scale = (float) height / (float) inBitmap.getHeight(); | ||||||
|  |             dx = (width - inBitmap.getWidth() * scale) * 0.5f * (1 + focalX); | ||||||
|  |             dy = 0; | ||||||
|  |         } else { | ||||||
|  |             scale = (float) width / (float) inBitmap.getWidth(); | ||||||
|  |             dx = 0; | ||||||
|  |             dy = (height - inBitmap.getHeight() * scale) * 0.5f * (1 + focalY); | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         m.setScale(scale, scale); | ||||||
|  |         m.postTranslate((int) (dx + 0.5f), (int) (dy + 0.5f)); | ||||||
|  | 
 | ||||||
|  |         Bitmap result = pool.get(width, height, getNonNullConfig(inBitmap)); | ||||||
|  |         // We don't add or remove alpha, so keep the alpha setting of the Bitmap we were given. | ||||||
|  |         TransformationUtils.setAlpha(inBitmap, result); | ||||||
|  | 
 | ||||||
|  |         applyMatrix(inBitmap, result, m); | ||||||
|  |         return result; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) { | ||||||
|  |         messageDigest.update((ID + focalX + focalY).getBytes(CHARSET)); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public boolean equals(Object o) { | ||||||
|  |         return o instanceof GlideFocus && | ||||||
|  |                 ((GlideFocus) o).focalX == focalX && | ||||||
|  |                 ((GlideFocus) o).focalY == focalY; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public int hashCode() { | ||||||
|  |         return (int) (ID.hashCode() + focalX * 100000 + focalY * 1000); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  |     @Override | ||||||
|  |     public String toString() { | ||||||
|  |         return "CropTransformation(width=" + focalX + ", height=" + focalY + ")"; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     private static final class NoLock implements Lock { | ||||||
|  | 
 | ||||||
|  |         @Synthetic | ||||||
|  |         NoLock() { | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void lock() { | ||||||
|  |             // do nothing | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void lockInterruptibly() throws InterruptedException { | ||||||
|  |             // do nothing | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public boolean tryLock() { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public boolean tryLock(long time, @NonNull TimeUnit unit) throws InterruptedException { | ||||||
|  |             return true; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @Override | ||||||
|  |         public void unlock() { | ||||||
|  |             // do nothing | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|  |         @NonNull | ||||||
|  |         @Override | ||||||
|  |         public Condition newCondition() { | ||||||
|  |             throw new UnsupportedOperationException("Should not be called"); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  | @ -117,6 +117,7 @@ import app.fedilab.android.databinding.LayoutMediaBinding; | ||||||
| import app.fedilab.android.databinding.LayoutPollItemBinding; | import app.fedilab.android.databinding.LayoutPollItemBinding; | ||||||
| import app.fedilab.android.exception.DBException; | import app.fedilab.android.exception.DBException; | ||||||
| import app.fedilab.android.helper.CrossActionHelper; | import app.fedilab.android.helper.CrossActionHelper; | ||||||
|  | import app.fedilab.android.helper.GlideFocus; | ||||||
| import app.fedilab.android.helper.Helper; | import app.fedilab.android.helper.Helper; | ||||||
| import app.fedilab.android.helper.LongClickLinkMovementMethod; | import app.fedilab.android.helper.LongClickLinkMovementMethod; | ||||||
| import app.fedilab.android.helper.MastodonHelper; | import app.fedilab.android.helper.MastodonHelper; | ||||||
|  | @ -984,10 +985,18 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|                 } else { |                 } else { | ||||||
|                     layoutMediaBinding.playMusic.setVisibility(View.GONE); |                     layoutMediaBinding.playMusic.setVisibility(View.GONE); | ||||||
|                 } |                 } | ||||||
|  |                 float focusX = 0.f; | ||||||
|  |                 float focusY = 0.f; | ||||||
|  |                 if (statusToDeal.media_attachments.get(0).meta != null && statusToDeal.media_attachments.get(0).meta.focus != null) { | ||||||
|  |                     focusX = statusToDeal.media_attachments.get(0).meta.focus.x; | ||||||
|  |                     focusY = statusToDeal.media_attachments.get(0).meta.focus.y; | ||||||
|  |                 } | ||||||
|  | 
 | ||||||
|                 if (!mediaObfuscated(statusToDeal) || expand_media) { |                 if (!mediaObfuscated(statusToDeal) || expand_media) { | ||||||
|                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24); |                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_24); | ||||||
|                     Glide.with(layoutMediaBinding.media.getContext()) |                     Glide.with(layoutMediaBinding.media.getContext()) | ||||||
|                             .load(statusToDeal.media_attachments.get(0).preview_url) |                             .load(statusToDeal.media_attachments.get(0).preview_url) | ||||||
|  |                             .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||||
|                             .into(layoutMediaBinding.media); |                             .into(layoutMediaBinding.media); | ||||||
|                 } else { |                 } else { | ||||||
|                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); |                     layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); | ||||||
|  | @ -995,6 +1004,7 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|                             .load(statusToDeal.media_attachments.get(0).preview_url) |                             .load(statusToDeal.media_attachments.get(0).preview_url) | ||||||
|                             .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) |                             .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) | ||||||
|                             // .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) |                             // .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||||
|  |                             .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||||
|                             .into(layoutMediaBinding.media); |                             .into(layoutMediaBinding.media); | ||||||
|                 } |                 } | ||||||
|                 layoutMediaBinding.viewHide.setOnClickListener(v -> { |                 layoutMediaBinding.viewHide.setOnClickListener(v -> { | ||||||
|  | @ -1009,6 +1019,13 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|                 for (Attachment attachment : statusToDeal.media_attachments) { |                 for (Attachment attachment : statusToDeal.media_attachments) { | ||||||
|                     LayoutMediaBinding layoutMediaBinding = LayoutMediaBinding.inflate(LayoutInflater.from(context), holder.binding.attachmentsList, false); |                     LayoutMediaBinding layoutMediaBinding = LayoutMediaBinding.inflate(LayoutInflater.from(context), holder.binding.attachmentsList, false); | ||||||
|                     RelativeLayout.LayoutParams lp; |                     RelativeLayout.LayoutParams lp; | ||||||
|  |                     float focusX = 0.f; | ||||||
|  |                     float focusY = 0.f; | ||||||
|  |                     if (statusToDeal.media_attachments.get(0).meta != null && statusToDeal.media_attachments.get(0).meta.focus != null) { | ||||||
|  |                         focusX = statusToDeal.media_attachments.get(0).meta.focus.x; | ||||||
|  |                         focusY = statusToDeal.media_attachments.get(0).meta.focus.y; | ||||||
|  |                     } | ||||||
|  | 
 | ||||||
|                     if (fullAttachement) { |                     if (fullAttachement) { | ||||||
|                         lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); |                         lp = new RelativeLayout.LayoutParams(RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT); | ||||||
|                         layoutMediaBinding.media.setScaleType(ImageView.ScaleType.FIT_CENTER); |                         layoutMediaBinding.media.setScaleType(ImageView.ScaleType.FIT_CENTER); | ||||||
|  | @ -1032,12 +1049,14 @@ public class StatusAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> | ||||||
|                         Glide.with(layoutMediaBinding.media.getContext()) |                         Glide.with(layoutMediaBinding.media.getContext()) | ||||||
|                                 .load(attachment.preview_url) |                                 .load(attachment.preview_url) | ||||||
|                                 .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) |                                 .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||||
|  |                                 .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||||
|                                 .into(layoutMediaBinding.media); |                                 .into(layoutMediaBinding.media); | ||||||
|                     } else { |                     } else { | ||||||
|                         layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); |                         layoutMediaBinding.viewHide.setImageResource(R.drawable.ic_baseline_visibility_off_24); | ||||||
|                         Glide.with(layoutMediaBinding.media.getContext()) |                         Glide.with(layoutMediaBinding.media.getContext()) | ||||||
|                                 .load(attachment.preview_url) |                                 .load(attachment.preview_url) | ||||||
|                                 .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) |                                 .apply(new RequestOptions().transform(new BlurTransformation(50, 3))) | ||||||
|  |                                 .apply(new RequestOptions().transform(new GlideFocus(focusX, focusY))) | ||||||
|                                 //    .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) |                                 //    .apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners((int) Helper.convertDpToPixel(3, context)))) | ||||||
|                                 .into(layoutMediaBinding.media); |                                 .into(layoutMediaBinding.media); | ||||||
|                     } |                     } | ||||||
|  |  | ||||||
		Loading…
	
		Reference in a new issue