[SOLVED]
After searching for an answer, I didn't find a solution for turning on the flash when in picture mode.
The app opens the camera in the background, and continulsy processes the pictures and detects objects, but the phone is located in a container which doesn't have light there, thus I need to make sure the flash is always opened.
There can be other approaches I'm considering as well and I'm not sure how to get these approaches to work also:
Switch to Video Mode. (Because I'm processing the pictures of the camera preview anyway, and in video mode the flash mode can work w/o recording a vide).
Set the camera default app to different app which supports image preview with flash when tapping on screen (I'll need to figure out how to switch to different app and how to simulate tapping, maybe even with another device which is connected to the app w/ bluetooth and sends clicks).
Override camera's API and make sure the Flash can be on, or just disabled and let another app turn on the flash.
This doesn't seem to work: (in the last code block)
Camera.Parameters parameters = camera.getParameters();
parameters.setFlashMode(Camera.Parameters.FLASH_MODE_ON);
Solution 1 or 3 should be ideal, any ideas how to make it work? This is the code I'm using:
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.DialogFragment;
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
import android.content.res.Configuration;
import android.graphics.ImageFormat;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCaptureSession;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.CaptureRequest;
import android.hardware.camera2.CaptureResult;
import android.hardware.camera2.TotalCaptureResult;
import android.hardware.camera2.params.StreamConfigurationMap;
import android.media.ImageReader;
import android.media.ImageReader.OnImageAvailableListener;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.text.TextUtils;
import android.util.Size;
import android.util.SparseIntArray;
import android.view.LayoutInflater;
import android.view.Surface;
import android.view.TextureView;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;
import org.tensorflow.lite.examples.classification.customview.AutoFitTextureView;
import org.tensorflow.lite.examples.classification.env.Logger;
public class CameraConnectionFragment extends Fragment {
private static final Logger LOGGER = new Logger();
/**
* The camera preview size will be chosen to be the smallest frame by pixel size capable of
* containing a DESIRED_SIZE x DESIRED_SIZE square.
*/
private static final int MINIMUM_PREVIEW_SIZE = 320;
/** Conversion from screen rotation to JPEG orientation. */
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
private static final String FRAGMENT_DIALOG = "dialog";
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
/** A {#link Semaphore} to prevent the app from exiting before closing the camera. */
private final Semaphore cameraOpenCloseLock = new Semaphore(1);
/** A {#link OnImageAvailableListener} to receive frames as they are available. */
private final OnImageAvailableListener imageListener;
/** The input size in pixels desired by TensorFlow (width and height of a square bitmap). */
private final Size inputSize;
/** The layout identifier to inflate for this Fragment. */
private final int layout;
private final ConnectionCallback cameraConnectionCallback;
private final CameraCaptureSession.CaptureCallback captureCallback =
new CameraCaptureSession.CaptureCallback() {
#Override
public void onCaptureProgressed(
final CameraCaptureSession session,
final CaptureRequest request,
final CaptureResult partialResult) {}
#Override
public void onCaptureCompleted(
final CameraCaptureSession session,
final CaptureRequest request,
final TotalCaptureResult result) {}
};
/** ID of the current {#link CameraDevice}. */
private String cameraId;
/** An {#link AutoFitTextureView} for camera preview. */
private AutoFitTextureView textureView;
/** A {#link CameraCaptureSession } for camera preview. */
private CameraCaptureSession captureSession;
/** A reference to the opened {#link CameraDevice}. */
private CameraDevice cameraDevice;
/** The rotation in degrees of the camera sensor from the display. */
private Integer sensorOrientation;
/** The {#link Size} of camera preview. */
private Size previewSize;
/** An additional thread for running tasks that shouldn't block the UI. */
private HandlerThread backgroundThread;
/** A {#link Handler} for running tasks in the background. */
private Handler backgroundHandler;
/**
* {#link TextureView.SurfaceTextureListener} handles several lifecycle events on a {#link
* TextureView}.
*/
private final TextureView.SurfaceTextureListener surfaceTextureListener =
new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(
final SurfaceTexture texture, final int width, final int height) {
openCamera(width, height);
}
#Override
public void onSurfaceTextureSizeChanged(
final SurfaceTexture texture, final int width, final int height) {
configureTransform(width, height);
}
#Override
public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
return true;
}
#Override
public void onSurfaceTextureUpdated(final SurfaceTexture texture) {}
};
/** An {#link ImageReader} that handles preview frame capture. */
private ImageReader previewReader;
/** {#link CaptureRequest.Builder} for the camera preview */
private CaptureRequest.Builder previewRequestBuilder;
/** {#link CaptureRequest} generated by {#link #previewRequestBuilder} */
private CaptureRequest previewRequest;
/** {#link CameraDevice.StateCallback} is called when {#link CameraDevice} changes its state. */
private final CameraDevice.StateCallback stateCallback =
new CameraDevice.StateCallback() {
#Override
public void onOpened(final CameraDevice cd) {
// This method is called when the camera is opened. We start camera preview here.
cameraOpenCloseLock.release();
cameraDevice = cd;
createCameraPreviewSession();
}
#Override
public void onDisconnected(final CameraDevice cd) {
cameraOpenCloseLock.release();
cd.close();
cameraDevice = null;
}
#Override
public void onError(final CameraDevice cd, final int error) {
cameraOpenCloseLock.release();
cd.close();
cameraDevice = null;
final Activity activity = getActivity();
if (null != activity) {
activity.finish();
}
}
};
#SuppressLint("ValidFragment")
private CameraConnectionFragment(
final ConnectionCallback connectionCallback,
final OnImageAvailableListener imageListener,
final int layout,
final Size inputSize) {
this.cameraConnectionCallback = connectionCallback;
this.imageListener = imageListener;
this.layout = layout;
this.inputSize = inputSize;
}
/**
* Given {#code choices} of {#code Size}s supported by a camera, chooses the smallest one whose
* width and height are at least as large as the minimum of both, or an exact match if possible.
*
* #param choices The list of sizes that the camera supports for the intended output class
* #param width The minimum desired width
* #param height The minimum desired height
* #return The optimal {#code Size}, or an arbitrary one if none were big enough
*/
protected static Size chooseOptimalSize(final Size[] choices, final int width, final int height) {
final int minSize = Math.max(Math.min(width, height), MINIMUM_PREVIEW_SIZE);
final Size desiredSize = new Size(width, height);
// Collect the supported resolutions that are at least as big as the preview Surface
boolean exactSizeFound = false;
final List<Size> bigEnough = new ArrayList<Size>();
final List<Size> tooSmall = new ArrayList<Size>();
for (final Size option : choices) {
if (option.equals(desiredSize)) {
// Set the size but don't return yet so that remaining sizes will still be logged.
exactSizeFound = true;
}
if (option.getHeight() >= minSize && option.getWidth() >= minSize) {
bigEnough.add(option);
} else {
tooSmall.add(option);
}
}
LOGGER.i("Desired size: " + desiredSize + ", min size: " + minSize + "x" + minSize);
LOGGER.i("Valid preview sizes: [" + TextUtils.join(", ", bigEnough) + "]");
LOGGER.i("Rejected preview sizes: [" + TextUtils.join(", ", tooSmall) + "]");
if (exactSizeFound) {
LOGGER.i("Exact size match found.");
return desiredSize;
}
// Pick the smallest of those, assuming we found any
if (bigEnough.size() > 0) {
final Size chosenSize = Collections.min(bigEnough, new CompareSizesByArea());
LOGGER.i("Chosen size: " + chosenSize.getWidth() + "x" + chosenSize.getHeight());
return chosenSize;
} else {
LOGGER.e("Couldn't find any suitable preview size");
return choices[0];
}
}
public static CameraConnectionFragment newInstance(
final ConnectionCallback callback,
final OnImageAvailableListener imageListener,
final int layout,
final Size inputSize) {
return new CameraConnectionFragment(callback, imageListener, layout, inputSize);
}
/**
* Shows a {#link Toast} on the UI thread.
*
* #param text The message to show
*/
private void showToast(final String text) {
final Activity activity = getActivity();
if (activity != null) {
activity.runOnUiThread(
new Runnable() {
#Override
public void run() {
Toast.makeText(activity, text, Toast.LENGTH_SHORT).show();
}
});
}
}
#Override
public View onCreateView(
final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(layout, container, false);
}
#Override
public void onViewCreated(final View view, final Bundle savedInstanceState) {
textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
}
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (textureView.isAvailable()) {
openCamera(textureView.getWidth(), textureView.getHeight());
} else {
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
}
#Override
public void onPause() {
closeCamera();
stopBackgroundThread();
super.onPause();
}
public void setCamera(String cameraId) {
this.cameraId = cameraId;
}
/** Sets up member variables related to camera. */
private void setUpCameraOutputs() {
final Activity activity = getActivity();
final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
final CameraCharacteristics characteristics = manager.getCameraCharacteristics(cameraId);
final StreamConfigurationMap map =
characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP);
sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION);
// Danger, W.R.! Attempting to use too large a preview size could exceed the camera
// bus' bandwidth limitation, resulting in gorgeous previews but the storage of
// garbage capture data.
previewSize =
chooseOptimalSize(
map.getOutputSizes(SurfaceTexture.class),
inputSize.getWidth(),
inputSize.getHeight());
// We fit the aspect ratio of TextureView to the size of preview we picked.
final int orientation = getResources().getConfiguration().orientation;
if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
textureView.setAspectRatio(previewSize.getWidth(), previewSize.getHeight());
textureView.setVisibility(View.GONE);
} else {
textureView.setAspectRatio(previewSize.getHeight(), previewSize.getWidth());
textureView.setVisibility(View.GONE);
}
} catch (final CameraAccessException e) {
LOGGER.e(e, "Exception!");
} catch (final NullPointerException e) {
// Currently an NPE is thrown when the Camera2API is used but not supported on the
// device this code runs.
// TODO(andrewharp): abstract ErrorDialog/RuntimeException handling out into new method and
// reuse throughout app.
ErrorDialog.newInstance(getString(R.string.camera_error))
.show(getChildFragmentManager(), FRAGMENT_DIALOG);
throw new RuntimeException(getString(R.string.camera_error));
}
cameraConnectionCallback.onPreviewSizeChosen(previewSize, sensorOrientation);
}
/** Opens the camera specified by {#link CameraConnectionFragment#cameraId}. */
private void openCamera(final int width, final int height) {
setUpCameraOutputs();
configureTransform(width, height);
final Activity activity = getActivity();
final CameraManager manager = (CameraManager) activity.getSystemService(Context.CAMERA_SERVICE);
try {
if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
throw new RuntimeException("Time out waiting to lock camera opening.");
}
manager.openCamera(cameraId, stateCallback, backgroundHandler);
} catch (final CameraAccessException e) {
LOGGER.e(e, "Exception!");
} catch (final InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera opening.", e);
}
}
/** Closes the current {#link CameraDevice}. */
private void closeCamera() {
try {
cameraOpenCloseLock.acquire();
if (null != captureSession) {
captureSession.close();
captureSession = null;
}
if (null != cameraDevice) {
cameraDevice.close();
cameraDevice = null;
}
if (null != previewReader) {
previewReader.close();
previewReader = null;
}
} catch (final InterruptedException e) {
throw new RuntimeException("Interrupted while trying to lock camera closing.", e);
} finally {
cameraOpenCloseLock.release();
}
}
/** Starts a background thread and its {#link Handler}. */
private void startBackgroundThread() {
backgroundThread = new HandlerThread("ImageListener");
backgroundThread.start();
backgroundHandler = new Handler(backgroundThread.getLooper());
}
/** Stops the background thread and its {#link Handler}. */
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
backgroundHandler = null;
} catch (final InterruptedException e) {
LOGGER.e(e, "Exception!");
}
}
/** Creates a new {#link CameraCaptureSession} for camera preview. */
private void createCameraPreviewSession() {
try {
final SurfaceTexture texture = textureView.getSurfaceTexture();
assert texture != null;
// We configure the size of default buffer to be the size of camera preview we want.
texture.setDefaultBufferSize(previewSize.getWidth(), previewSize.getHeight());
// This is the output Surface we need to start preview.
final Surface surface = new Surface(texture);
// We set up a CaptureRequest.Builder with the output Surface.
previewRequestBuilder = cameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
previewRequestBuilder.addTarget(surface);
LOGGER.i("Opening camera preview: " + previewSize.getWidth() + "x" + previewSize.getHeight());
// Create the reader for the preview frames.
previewReader =
ImageReader.newInstance(
previewSize.getWidth(), previewSize.getHeight(), ImageFormat.YUV_420_888, 2);
previewReader.setOnImageAvailableListener(imageListener, backgroundHandler);
previewRequestBuilder.addTarget(previewReader.getSurface());
// Here, we create a CameraCaptureSession for camera preview.
cameraDevice.createCaptureSession(
Arrays.asList(surface, previewReader.getSurface()),
new CameraCaptureSession.StateCallback() {
#Override
public void onConfigured(final CameraCaptureSession cameraCaptureSession) {
// The camera is already closed
if (null == cameraDevice) {
return;
}
// When the session is ready, we start displaying the preview.
captureSession = cameraCaptureSession;
try {
// Auto focus should be continuous for camera preview.
previewRequestBuilder.set(
CaptureRequest.CONTROL_AF_MODE,
CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
// Flash is automatically enabled when necessary.
// previewRequestBuilder.set(
// CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH);
previewRequestBuilder.set(
CaptureRequest.FLASH_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
// Finally, we start displaying the camera preview.
previewRequest = previewRequestBuilder.build();
captureSession.setRepeatingRequest(
previewRequest, captureCallback, backgroundHandler);
} catch (final CameraAccessException e) {
LOGGER.e(e, "Exception!");
}
}
#Override
public void onConfigureFailed(final CameraCaptureSession cameraCaptureSession) {
showToast("Failed");
}
},
null);
} catch (final CameraAccessException e) {
LOGGER.e(e, "Exception!");
}
}
}
}
The second one:
public class LegacyCameraConnectionFragment extends Fragment {
private static final Logger LOGGER = new Logger();
/** Conversion from screen rotation to JPEG orientation. */
private static final SparseIntArray ORIENTATIONS = new SparseIntArray();
static {
ORIENTATIONS.append(Surface.ROTATION_0, 90);
ORIENTATIONS.append(Surface.ROTATION_90, 0);
ORIENTATIONS.append(Surface.ROTATION_180, 270);
ORIENTATIONS.append(Surface.ROTATION_270, 180);
}
private Camera camera;
private Camera.PreviewCallback imageListener;
private Size desiredSize;
/** The layout identifier to inflate for this Fragment. */
private int layout;
/** An {#link AutoFitTextureView} for camera preview. */
private AutoFitTextureView textureView;
/**
* {#link TextureView.SurfaceTextureListener} handles several lifecycle events on a {#link
* TextureView}.
*/
private final TextureView.SurfaceTextureListener surfaceTextureListener =
new TextureView.SurfaceTextureListener() {
#Override
public void onSurfaceTextureAvailable(
final SurfaceTexture texture, final int width, final int height) {
int index = getCameraId();
camera = Camera.open(index);
try {
Camera.Parameters parameters = camera.getParameters();
List<String> focusModes = parameters.getSupportedFocusModes();
if (focusModes != null
&& focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) {
parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE);
}
List<Camera.Size> cameraSizes = parameters.getSupportedPreviewSizes();
Size[] sizes = new Size[cameraSizes.size()];
int i = 0;
for (Camera.Size size : cameraSizes) {
sizes[i++] = new Size(size.width, size.height);
}
Size previewSize =
CameraConnectionFragment.chooseOptimalSize(
sizes, desiredSize.getWidth(), desiredSize.getHeight());
parameters.setPreviewSize(previewSize.getWidth(), previewSize.getHeight());
camera.setDisplayOrientation(90);
camera.setParameters(parameters);
camera.setPreviewTexture(texture);
} catch (IOException exception) {
camera.release();
}
camera.setPreviewCallbackWithBuffer(imageListener);
Camera.Size s = camera.getParameters().getPreviewSize();
camera.addCallbackBuffer(new byte[ImageUtils.getYUVByteSize(s.height, s.width)]);
textureView.setAspectRatio(s.height, s.width);
camera.startPreview();
}
#Override
public void onSurfaceTextureSizeChanged(
final SurfaceTexture texture, final int width, final int height) {}
#Override
public boolean onSurfaceTextureDestroyed(final SurfaceTexture texture) {
return true;
}
#Override
public void onSurfaceTextureUpdated(final SurfaceTexture texture) {}
};
/** An additional thread for running tasks that shouldn't block the UI. */
private HandlerThread backgroundThread;
#SuppressLint("ValidFragment")
public LegacyCameraConnectionFragment(
final Camera.PreviewCallback imageListener, final int layout, final Size desiredSize) {
this.imageListener = imageListener;
this.layout = layout;
this.desiredSize = desiredSize;
}
#Override
public View onCreateView(
final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) {
return inflater.inflate(layout, container, false);
}
#Override
public void onViewCreated(final View view, final Bundle savedInstanceState) {
textureView = (AutoFitTextureView) view.findViewById(R.id.texture);
}
#Override
public void onActivityCreated(final Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
}
#Override
public void onResume() {
super.onResume();
startBackgroundThread();
// When the screen is turned off and turned back on, the SurfaceTexture is already
// available, and "onSurfaceTextureAvailable" will not be called. In that case, we can open
// a camera and start preview from here (otherwise, we wait until the surface is ready in
// the SurfaceTextureListener).
if (textureView.isAvailable()) {
camera.startPreview();
} else {
textureView.setSurfaceTextureListener(surfaceTextureListener);
}
}
#Override
public void onPause() {
stopCamera();
stopBackgroundThread();
super.onPause();
}
/** Starts a background thread and its {#link Handler}. */
private void startBackgroundThread() {
backgroundThread = new HandlerThread("CameraBackground");
backgroundThread.start();
}
/** Stops the background thread and its {#link Handler}. */
private void stopBackgroundThread() {
backgroundThread.quitSafely();
try {
backgroundThread.join();
backgroundThread = null;
} catch (final InterruptedException e) {
LOGGER.e(e, "Exception!");
}
}
protected void stopCamera() {
if (camera != null) {
camera.stopPreview();
camera.setPreviewCallback(null);
camera.release();
camera = null;
}
}
private int getCameraId() {
CameraInfo ci = new CameraInfo();
for (int i = 0; i < Camera.getNumberOfCameras(); i++) {
Camera.getCameraInfo(i, ci);
if (ci.facing == CameraInfo.CAMERA_FACING_BACK) return i;
}
return -1; // No camera found
}
}
SOLUTION: in second block code:
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON);
mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,
CaptureRequest.FLASH_MODE_TORCH);
and in first block code:
//Check Whether device supports AutoFlash, If you YES then set AutoFlash
List<String> flashModes = parameters.getSupportedFlashModes();
if (flashModes.contains(android.hardware.Camera.Parameters.FLASH_MODE_AUTO))
{
parameters.setFlashMode(parameters.FLASH_MODE_AUTO);
}
SOLUTION: in second block code:
mPreviewRequestBuilder.set(CaptureRequest.CONTROL_AE_MODE,
CaptureRequest.CONTROL_AE_MODE_ON);
mPreviewRequestBuilder.set(CaptureRequest.FLASH_MODE,
CaptureRequest.FLASH_MODE_TORCH);
and in first block code:
//Check Whether device supports AutoFlash, If you YES then set AutoFlash
List<String> flashModes = parameters.getSupportedFlashModes();
if (flashModes.contains(android.hardware.Camera.Parameters.FLASH_MODE_AUTO))
{
parameters.setFlashMode(parameters.FLASH_MODE_AUTO);
}
I have a ThirdPartyEntity from a third party bundle that, using a ThirdPartyEntityTrait, I link to MyEntity in my project.
Now, as the ThirdPartyEntity is not set a ApiResource nor as an ApiSubresource and as I don't have any serializaton group set on MyEntity, when I get MyEntity from ApiPlatform, it returns me something like this:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some value"
}
}
BUT IF I PUT a changed value for a_property with this body:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":22,
"a_property":"some NEW value to update"
}
}
I get a new third_party_entity to be created and get this response:
{
"#id":"/api/my_entities/17",
"#type":"MyEntity",
"id":17,
"third_party_entity": {
"id":23,
"a_property":"some NEW value to update"
}
}
SO, HOW CAN I UPDATE third_party_entity instead of creating it each time?
HERE THERE ARE THE INVOLVED CLASSES AND TRAITS
/**
* #ORM\Table(name="app_my_entities")
* #ORM\Entity()
* #ApiResource()
*/
class MyEntity
{
// !!!!!!!!!!!!!!!!!!
// This is the trait I use to link MyEntity
// with the entity from the third-party bundle
// !!!!!!!!!!!!!!!!!!
use ThirdPartyEntityTrait;
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
...
}
And this is the ThirdPartyEntityTrait:
trait ThirdPartyEntityTrait
{
/**
* #ORM\OneToOne(targetEntity="Namespace\To\Bundle\Entity\ThirdPartyEntity", cascade={"all"})
* #ORM\JoinColumn(name="thirdPartyEntity", referencedColumnName="id")
*/
private $thirdPartyEntity;
/**
* #param thirdPartyEntity $thirdPartyEntity
*
* #return ThirdPartyEntity
*/
public function setThirdPartyEntity(thirdPartyEntity $thirdPartyEntity): ThirdPartyEntity
{
$this->thirdPartyEntity = $thirdPartyEntity;
/** #var ThirdPartyEntity $this */
return $this;
}
/**
* #return thirdPartyEntity
*/
public function getThirdPartyEntity(): ?thirdPartyEntity
{
return $this->thirdPartyEntity;
}
/**
* #return thirdPartyEntity
*/
public function removeThirdPartyEntity(): ?thirdPartyEntity
{
$thirdPartyEntity = $this->getThirdPartyEntity();
$this->thirdPartyEntity = null;
return $thirdPartyEntity;
}
}
As you can see, nothing more a property to save the relation and some accessors methods.
This is, instead, the linked Entity:
/**
* #ORM\Entity()
* #ORM\Table(name="third_party_entities")
*/
class ThirdPartyEntity
{
/**
* #var int
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #ORM\Column(name="aProperty", type="string", nullable=true)
*/
private $aProperty;
public function getId()
{
return $this->id;
}
public function getAProperty()
{
return $this->aProperty;
}
public function setAProperty($aProperty)
{
$this->aProperty = $aProperty;
return $this;
}
}
This question is cross-posted also on GitHub.
The solution was pretty simple: use another config method!
Practically, it is possible to mix configuration types and so, it is possible to use the annotations along with the yaml configuration.
Given this, it is sufficient to create a new config file in config/api_platform/third_party_entity.yaml.
In it put the configuration required to map the entity from the third party bundle:
resources:
App\Entity\MyEntity:
properties:
remote:
subresource:
resourceClass: 'Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity'
Third\Party\Bundle\TheBundle\Entity\ThirdPartyEntity:
This way it is possible to configure as subresource the entity from the third party bundle to which we don't have access with annotations.
My current problem is the following: I have a RecyclerView and each element of this RecyclerView has a TextView. I want that the TextView scrolls automatically. What if tried so far:
Customize TextView:
public class ScrollCustomTextView extends AppCompatTextView {
public ScrollCustomTextView(Context context) {
super(context);
}
public ScrollCustomTextView(Context context, AttributeSet attrs, int defStyle) {
super(context,attrs,defStyle);
}
public ScrollCustomTextView(Context context, AttributeSet attrs) {
super(context,attrs);
}
#Override
protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
if (focused)
super.onFocusChanged(focused, direction, previouslyFocusedRect);
}
#Override
public void onWindowFocusChanged(boolean focused) {
if(focused)
super.onWindowFocusChanged(focused);
}
#Override
public boolean isFocused() {
return true;
}
}
and my .xml file looks like this:
<package.ScrollCustomTextView
android:id="#+id/main_idea_name"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="marquee_forever"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#color/darkGreen"
android:textSize="27dp"
android:layout_marginTop="10dp"
android:maxLines="1"
android:textAppearance="#android:style/TextAppearance.Medium"
/>
And in RecyclerView in the constructor for the ViewHolder I set
textView.setSelected(true);
But it doesn't work. Does anyone have an idea to solve this?
Thanks.
So here is the answer:
I put my TextView inside a HorizontalScrollView and added the following code to my adapter for the RecyclerView:
private void animateTextView(TextView mTextView) {
int textWidth = getTextViewWidth(mTextView);
int displayWidth = getDisplayWidth(mContext);
/* Start animation only when text is longer than dislay width. */
if(displayWidth<=textWidth) {
Animation mAnimation = new TranslateAnimation(
0, -textWidth,
0, 0);
mAnimation.setDuration(10000); // Set custom duration.
mAnimation.setStartOffset(1000); // Set custom offset.
mAnimation.setRepeatMode(Animation.RESTART); // This will animate text back ater it reaches end.
mAnimation.setRepeatCount(Animation.INFINITE); // Infinite animation.
mTextView.startAnimation(mAnimation);
}
}
private int getDisplayWidth(Context context) {
int displayWidth;
WindowManager windowManager = (WindowManager)context.getSystemService(
Context.WINDOW_SERVICE);
Display display = windowManager.getDefaultDisplay();
Point screenSize = new Point();
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.HONEYCOMB_MR2) {
display.getSize(screenSize);
displayWidth = screenSize.x;
} else {
displayWidth = display.getWidth();
}
return displayWidth;
}
private int getTextViewWidth(TextView textView) {
textView.measure(0, 0); // Need to set measure to (0, 0).
return textView.getMeasuredWidth();
}
And start the animation with:
animateTextView(txtView);
Note: there is no more need for the customized TextView, just use a normal TextView.
I'm using the VichUploadBundle to upload images into my database, and would like to have the thumbnails of the images displayed on the website with LiipImage.
This is my config.yml with the files set up
//.....
vich_uploader:
db_driver: orm
twig: true
mappings:
product_image:
uri_prefix: /images/products
upload_destination: '%kernel.root_dir%/../web/images/products'
inject_on_load: false
delete_on_update: true
delete_on_remove: true
liip_imagine:
# configure resolvers
resolvers:
# setup the default resolver
default:
# use the default web path
web_path: ~
# your filter sets are defined here
filter_sets:
# use the default cache configuration
cache: ~
# the name of the "filter set"
my_thumb:
# adjust the image quality to 75%
quality: 75
# list of transformations to apply (the "filters")
filters:
# create a thumbnail: set size to 120x90 and use the "outbound" mode
# to crop the image when the size ratio of the input differs
thumbnail: { size: [120, 90], mode: outbound }
# create a 2px black border: center the thumbnail on a black background
# 4px larger to create a 2px border around the final image
background: { size: [124, 98], position: center, color: '#000000' }
Most of the code for liip is copied straight up from the documentation for testing.
This is my routing.yml
app:
resource: "#AppBundle/Controller/"
type: annotation
_liip_imagine:
resource: "#LiipImagineBundle/Resources/config/routing.xml
Finally I have the Image entity I want to upload.
It's as well a code copied from the official Vich documentation
<?php
namespace AppBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\HttpFoundation\File\File;
use Vich\UploaderBundle\Mapping\Annotation as Vich;
/**
* #ORM\Entity
* #Vich\Uploadable
*/
class Image
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
// ..... other fields
/**
* NOTE: This is not a mapped field of entity metadata, just a simple property.
*
* #Vich\UploadableField(mapping="product_image", fileNameProperty="imageName")
*
* #var File
*/
protected $imageFile;
/**
* #ORM\Column(type="string", length=255)
*
* #var string
*/
protected $imageName;
/**
* #ORM\Column(type="string", length=100)
*
* #var string
*/
protected $imageTitle = null;
/**
* #ORM\Column(type="string", length=100)
*
* #var string
*/
protected $imageAuthor = null;
/**
* If manually uploading a file (i.e. not using Symfony Form) ensure an instance
* of 'UploadedFile' is injected into this setter to trigger the update. If this
* bundle's configuration parameter 'inject_on_load' is set to 'true' this setter
* must be able to accept an instance of 'File' as the bundle will inject one here
* during Doctrine hydration.
*
* #param File|\Symfony\Component\HttpFoundation\File\UploadedFile $image
*
* #return Product
*/
public function setImageFile(File $image = null)
{
$this->imageFile = $image;
if ($image) {
// It is required that at least one field changes if you are using doctrine
// otherwise the event listeners won't be called and the file is lost
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
/**
* #return File|null
*/
public function getImageFile()
{
return $this->imageFile;
}
/**
* #param string $imageName
*
* #return Product
*/
public function setImageName($imageName)
{
$this->imageName = $imageName;
return $this;
}
/**
* #return string|null
*/
public function getImageName()
{
return $this->imageName;
}
/**
* #ORM\Column(type="datetime")
*
* #var \DateTime
*/
protected $updatedAt;
/**
* #return null
*/
public function getImageTitle()
{
return $this->imageTitle;
}
/**
* #param null $imageTitle
*/
public function setImageTitle($imageTitle)
{
$this->imageTitle = $imageTitle;
}
/**
* #return null
*/
public function getImageAuthor()
{
return $this->imageAuthor;
}
/**
* #param null $imageAuthor
*/
public function setImageAuthor($imageAuthor)
{
$this->imageAuthor = $imageAuthor;
}
}
In my controller I have the image table put into array with $em
$em = $this->getDoctrine()->getManager();
$list = $em->getRepository(Image::class)->findAll();
Which I later put into the view and (would want to) print them like so:
{% for l in list %}
<tr>
<td>
<img src="{{ asset('/images/products/'~l.imageName) | imagine_filter('my_thumb') }}" alt="{{ l.imageName }}">
</td>
<td>{{ l.imageTitle }}</td>
<td>{{ l.imageAuthor }}</td>
</tr>
{% endfor %}
Unfortunately the output looks like this:
Now the path is fine. The image works well without the imagine_filter(). Same effect would occur when using the Vich Assets.
When checked the path the image is linked under is:
http://localhost:8000/media/cache/resolve/my_thumb/images/products/hqdefault.jpg
The path to images is
web/images/products
Is there anyone who know what might be the issue?
Or to provide an alternative bundle for creating image thumbnails
All help would be amazing
FIXED: Enabling GD and filenames in php.ini and restating apache, afterwards clearing the cache fixed everything
Im trying to draw a jpg to a canvas in onDraw() however the Bitmap type doesnt seem to be drawable (doesn't have the .draw method). My goal is to have a jpeg that is movable on the screen with a touch/drag. How do I need to draw this?
Here is my constructor (where i pass the image path to the view)
public TouchViewClass(Context context, AttributeSet attrs, int defStyle, String picPath) {
super(context, attrs, defStyle);
this.picPath=picPath;
}
Here is my onDraw
#Override
public void onDraw(Canvas canvas) {
super.onDraw(canvas);
Bitmap myImg = BitmapFactory.decodeFile(picPath);
canvas.save();
canvas.translate(mPosX, mPosY);
//Here is where the bitmap should be drawn
canvas.restore();
}
First of all, I would not recommend decoding file to bitmap in onDraw(). This method is called every single frame, so your draw process is going to be extremely slow. You need to decode your file beforehand (in your case in your constructor) and reuse this bitmap in onDraw.
As for actual drawing bitmap, you can easily draw bitmap by calling Canvas.drawBitmap() method.
So basically you don't even need to translate canvas - you can specify destination directly:
canvas.drawBitmap(bmp, mPosX, mPosY, null);
I would change the faulty implementation exposed to something like:
public class TouchViewClass extends View{
private Bitmap myImg;
private float mPosX = 0;
private float mPosY = 0;
private Paint paint = new Paint();
public float getmPosX() {
return mPosX;
}
public void setmPosX(float mPosX) {
this.mPosX = mPosX;
}
public float getmPosY() {
return mPosY;
}
public void setmPosY(float mPosY) {
this.mPosY = mPosY;
}
/**
* So you can inflate from XML
* #param context
* #param attrs
*/
public TouchViewClass(Context context, AttributeSet attrs) {
super(context, attrs);
}
/**
* So you can create in code
* #param context
* #param attrs
* #param picPath
*/
public TouchViewClass(Context context, AttributeSet attrs, String picPath) {
super(context, attrs);
setPicPath(context, picPath);
}
public void setPicPath(Context context, String picPath){
if(!TextUtils.isEmpty(picPath)){
myImg = BitmapFactory.decodeFile(picPath);
}else{
Resources resources = context.getResources();
myImg = BitmapFactory.decodeResource(resources, R.drawable.ic_launcher);
}
}
#Override
protected void onDraw(Canvas canvas) {
super.onDraw(canvas);
canvas.drawBitmap(myImg, mPosX, mPosY, paint);
}
}