{"id":1634,"date":"2013-09-17T20:42:54","date_gmt":"2013-09-17T08:42:54","guid":{"rendered":"https:\/\/www.deltics.co.nz\/blog\/?p=1634"},"modified":"2013-09-18T09:18:22","modified_gmt":"2013-09-17T21:18:22","slug":"an-app-with-view","status":"publish","type":"post","link":"https:\/\/www.deltics.co.nz\/blog\/posts\/1634\/","title":{"rendered":"An App With View"},"content":{"rendered":"<span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span><p>Not a <em>Merchant Ivory<\/em> production, but <strong>Part 3<\/strong> in the <strong>Oxygene for Java<\/strong> camera app for <strong>Android<\/strong> series.<\/p>\n<p>So far we have seen that we can <a href=\"https:\/\/www.deltics.co.nz\/blog\/?p=1605\">work directly with the <strong>Android<\/strong> platform manifest and layout files<\/a> and <a href=\"https:\/\/www.deltics.co.nz\/blog\/?p=1624\">how the <strong>Oxygene<\/strong> language is a first class citizen in the Java platform<\/a> and just one way in which it simplifies and improves the business of writing Java code, in Pascal.<\/p>\n<p>Now it&#8217;s time to make the app do something useful.<\/p>\n<p><!--more--><\/p>\n<h3>First Things Third<\/h3>\n<p>A camera is not very useful without some way of seeing what it is you are taking a picture of (<a href=\"https:\/\/theta360.com\/en\/concept\/\">with some exceptions<\/a>).  We need some sort of preview or viewfinder.<\/p>\n<p>In <a href=\"https:\/\/www.deltics.co.nz\/blog\/posts\/1605\">Part 1<\/a> we created a layout element &#8211; a <strong>FrameLayout<\/strong> &#8211; as a placeholder for a camera preview control, and now we need to create that control and place it in that UI element.  We shall do that in code.  But first we need to implement the preview control itself.  To keep things organised I put this new control class in a separate file (<strong>ViewFinder.pas<\/strong>) but declared the contents as part of the same <strong>namespace<\/strong> as the rest of the app.<\/p>\n<p>For what we have in mind I need to sub-class the Android <strong>SurfaceView<\/strong> class and implement the <strong>SurfaceHolder.Callback<\/strong> interface.  Our control will need to maintain a reference to the camera which will be supplied when we construct our viewfinder class.  A <strong>Camera.CameraInfo<\/strong> object will also be useful later on.  Finally we will need a reference to an entity known as a <strong>SurfaceHolder<\/strong>.<\/p>\n<p>Remember, this exercise has been a learning experience for me so I am no expert on the Android framework.  But if I have understood things correctly, <strong>SurfaceView<\/strong> is to <strong>TCustomControl<\/strong> as <strong>SurfaceHolder<\/strong> is to the <strong>TCanvas<\/strong>.  It&#8217;s not quite as direct a relationship as that, but for our purposes this will suffice.  The <strong>SurfaceView<\/strong> &#8211; via the <strong>SurfaceHolder<\/strong> &#8211; provides the &#8220;canvas&#8221; on which we will render our camera preview.  Our initial class declaration looks like this:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n\r\n  namespace nz.co.deltics.demo.camera;\r\n\r\ninterface\r\n\r\n  uses \r\n    android.content,\r\n    android.graphics,\r\n    android.hardware,\r\n    android.util,\r\n    android.view;\r\n\r\n  type\r\n    ViewFinder = public class(SurfaceView, SurfaceHolder.Callback)\r\n    private\r\n      mCamera: Camera;\r\n      mCameraInfo: Camera.CameraInfo;\r\n      mSurfaceHolder: SurfaceHolder;\r\n    public\r\n      constructor(aContext: Context; aCamera: Camera);\r\n\r\n    private \/\/ SurfaceHolder.Callback\r\n      method surfaceCreated(aHolder: SurfaceHolder);\r\n      method surfaceDestroyed(aHolder: SurfaceHolder);\r\n      method surfaceChanged(aHolder: SurfaceHolder; aFormat, aWidth, aHeight: Integer);\r\n    end;\r\n\r\n\r\nimplementation\r\n\r\n  \/\/ ...\r\n\r\n<\/pre>\n<p>I have added some <strong>Android<\/strong> namespaces to the <strong>uses<\/strong> list to avoid having to fully qualify references made to members of those namespaces.  This isn&#8217;t strictly necessary, and you can of course continue to fully qualify any references if necessary (or desired).<\/p>\n<p>For example, anywhere that <strong>Camera<\/strong> is referenced I could instead fully qualify the reference as <strong>android.hardware.Camera<\/strong> and then I would not need to &#8220;use&#8221; the <strong>android.hardware<\/strong> namespace.<\/p>\n<p>This is obviously similar to the concept of using a <strong>unit<\/strong> in <strong>Delphi<\/strong>, except that with Delphi you <strong><em>must<\/em><\/strong> use a unit in order to reference it&#8217;s contents where the same is not the case in <strong>Oxygene<\/strong>.  <strong>Oxygene<\/strong> is more similar to more modern languages such as <strong>Java<\/strong> and <strong>C#<\/strong> in this respect.<\/p>\n<p>So now let us look at the implementation of the class itself.<\/p>\n<p>First, the constructor.<\/p>\n<h3>Anonymous Constructors and Non-Existent Properties<\/h3>\n<p>Since an <strong>Oxygene<\/strong> class in <strong>Oxygene for Java<\/strong> is compiled to a <strong>Java<\/strong> class, it must conform to the rules of the <strong>Java<\/strong> language and accordingly our <strong>constructor<\/strong> has no name.  <strong>Oxygene<\/strong> optionally allows us to redundantly use a name of <strong>Create<\/strong> for our constructors if we wish but this is optional (on a per project basis, according to a <em>Compatibility<\/em> setting in the compiler options) and even with this option enabled <strong>Create<\/strong> is the only name allowed.<\/p>\n<p>I have opted to adopt the Java convention in this case and so no name is used.<\/p>\n<p>The implementation of the constructor itself is very straightforward.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  constructor ViewFinder(aContext: Context; aCamera: Camera);\r\n  begin\r\n    inherited constructor(aContext);\r\n\r\n    mCamera := aCamera;\r\n\r\n    mCameraInfo := new Camera.CameraInfo;\r\n    Camera.getCameraInfo(0, mCameraInfo);\r\n\r\n    mSurfaceHolder := self.Holder;\r\n    mSurfaceHolder.addCallback(self);\r\n  end;\r\n<\/pre>\n<p>After invoking the <strong>inherited constructor<\/strong> of the <strong>SurfaceView<\/strong> parent class a reference to the supplied <strong>Camera<\/strong> object is stored in the <strong>mCamera<\/strong> member which we will need later on.<\/p>\n<p>Next we create the <strong>Camera.CameraInfo<\/strong> object and obtain this info from the <strong>Camera<\/strong>.  Notice in this case that the &#8220;getCameraInfo&#8221; method does not conform to the semantics of a property (it modifies a passed parameter rather than returning a result) and we must call it as a method.  In a real application we would need to be more sensible about how we determine which camera to get info for, but for my purposes in this app I know that the camera I want (the rear facing camera) is the first camera on my device (an ASUS TF101).  So I&#8217;m being quick and dirty and just specifying camera 0 (zero).  Not recommended, obviously.  \ud83d\ude42<\/p>\n<p>We will use the information in this info object later on to ensure our preview image is correctly oriented with respect to the device orientation.<\/p>\n<p>We then obtain a reference to the <strong>SurfaceHolder<\/strong> of the <strong>SurfaceView<\/strong> via the <strong>Holder<\/strong> property and we add a reference to the <strong>ViewFinder<\/strong> class itself as a callback handler for the <strong>SurfaceHolder<\/strong>.<\/p>\n<p>Yet again, this is where <strong>Oxygene<\/strong> does not simply provide access to the <strong>Java<\/strong> classes but allows us to work with them in a manner more natural to an <strong>ObjectPascal<\/strong> developer.  As far as I know, <strong>Java<\/strong> still does not formally support the notion of a &#8220;property&#8221; on a class, as a Delphi developer would be used to.  The &#8220;<strong>Holder<\/strong>&#8221; property we are reading in this constructor is in fact a regular <strong>Java method<\/strong> named <strong>getHolder<\/strong>.  As I understand it however, it is a well established convention in <strong>Java<\/strong> (stemming from Java Beans) to name accessor methods for notional properties in this way.   <strong>Oxygene<\/strong> takes that convention and presents it to the developer as if the <strong>Java<\/strong> class really did have a property formally declared.<\/p>\n<p>But again, you have the choice.  Although the code completion in Visual Studio offers only the &#8220;property name&#8221;, the compiler does not force you to use the property syntax.  If you prefer you can still use the formal method name.  The two are exactly equivalent:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n    mSurfaceHolder := self.Holder;\r\n    mSurfaceHolder := self.getHolder;\r\n<\/pre>\n<p>Now let&#8217;s look at the implementation of the <strong>SurfaceHolder.Callback<\/strong> interface, which is where all the real work is done.<\/p>\n<h3>It&#8217;s For You-hooo<\/h3>\n<p>The <strong>surfaceCreated<\/strong> and <strong>surfaceDestroyed<\/strong> methods of the callback interface notify us when the <strong>Surface<\/strong> of our <strong>SurfaceView<\/strong> control has been, predictably enough, created or destroyed.  In our case these notifications tell us when to start and stop displaying preview images taken through the camera.<\/p>\n<p>First, <strong>surfaceCreated<\/strong>: <\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method ViewFinder.surfaceCreated(aHolder: SurfaceHolder);\r\n  begin\r\n    \/\/ The Surface has been created, now tell the camera where to draw the preview.\r\n    try \r\n      mCamera.PreviewDisplay := mSurfaceHolder;\r\n      mCamera.startPreview;\r\n\r\n    except\r\n      on e: IOException do\r\n        Log.d(&quot;Demo Camera&quot;, &quot;Error setting camera preview: &quot; + e.getMessage());\r\n    end;\r\n  end;\r\n<\/pre>\n<p>When our control&#8217;s surface is created we assign our <strong>SurfaceHolder<\/strong> to the <strong>PreviewDisplay<\/strong> property of the <strong>Camera<\/strong> object reference (stored by our constructor).  After assigning the <strong>PreviewDisplay<\/strong> we then call <strong>startPreview<\/strong> on the camera to, well, start capturing preview images, which the camera will then automatically start presenting for us.<\/p>\n<p>In a manner that will be familiar to any Delphi developer, We keep a wary eye open for an <strong>IOException<\/strong> and if we get one we log it for potential diagnostic purposes.<\/p>\n<p>Quickly going back to the <strong>PreviewDisplay<\/strong> property.  This is <strong>Java<\/strong>, remember.  No such thing as properties.  And that&#8217;s right.  Again, there is no such &#8220;property&#8221; called &#8220;<strong>PreviewDisplay<\/strong>&#8221; on the <strong>Camera<\/strong>.  There are <strong>getPreviewDisplay<\/strong> and <strong>setPreviewDisplay<\/strong> methods however.<\/p>\n<p><strong>Oxygene<\/strong> doesn&#8217;t just sanitise get methods into readable properties, it does the same for <em>writable<\/em> ones (with a <strong>set<\/strong> method) too ! \ud83d\ude42  Just as before however, you do still have the option of using the explicit <strong>setPreviewDisplay()<\/strong> method call syntax if you prefer:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n      mCamera.setPreviewDisplay(mSurfaceHolder);\r\n<\/pre>\n<p>Worth mentioning at this point is that as well as setting up automatic drawing of preview frames, if we were interested in working with the images captured <em>during<\/em> preview there are mechanisms that enable us to receive additional notifications which receive each image captured during preview, but that is not something that we are concerned with here.<\/p>\n<p>So much for getting the preview going.<\/p>\n<p>When our surface is destroyed we must be sure to stop the preview as well, and that is take care of in <strong>surfaceDestroyed<\/strong>:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method ViewFinder.surfaceDestroyed(aHolder: SurfaceHolder);\r\n  begin\r\n    mCamera.stopPreview;\r\n    mCamera.release;\r\n  end;\r\n<\/pre>\n<p>We tell the camera to stop the preview and release the camera.<\/p>\n<p>The final notification from the SurfaceHolder that we need to respond to, is when the surface has changed.  One of the reasons that our surface may change is when the device orientation changes, causing our layout to be rotated.  When this occurs I would like to ensure that the preview image is oriented correctly with respect to the camera &#8211; this is not something that occurs automatically (and perhaps is not always appropriate though it is in my case and I can&#8217;t think off-hand of a situation when it might not be).<\/p>\n<p>In any event, to determine the orientation to be applied to the camera we must determine the current orientation of the <em>device<\/em> and make the appropriate adjustment according to the <em>natural orientation<\/em> of the camera.  The information about the camera is fixed and is contained in the <strong>Camera.CameraInfo<\/strong> object we obtained in our constructor.  We did that so that we would not need to query the camera for that information every time the device orientation changes and so can respond that bit more quickly.<\/p>\n<p>Obtaining the current device orientation involves a bit of hoop jumping to obtain a reference to the system <strong>WindowManager<\/strong> so that we may obtain the current rotation from the <strong>DefaultDisplay<\/strong>.<\/p>\n<p>With the current device rotation we can calculate the adjustment for the camera, based on the camera&#8217;s natural orientation.  Which is all a very wordy way of saying:<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method ViewFinder.surfaceChanged(aHolder: SurfaceHolder; aFormat, aWidth, aHeight: Integer);\r\n  var\r\n    wndmgr: WindowManager;\r\n    rotation: Integer;\r\n    angle: Integer;\r\n  begin\r\n    if NOT assigned(mSurfaceHolder.Surface) then\r\n      EXIT; \/\/ preview surface does not exist\r\n    \r\n    wndmgr   := WindowManager(self.Context.getSystemService(Context.WINDOW_SERVICE));\r\n    rotation := wndmgr.DefaultDisplay.Rotation;\r\n\r\n    case rotation of\r\n      Surface.ROTATION_0  : angle := 0;\r\n      Surface.ROTATION_90 : angle := 90;\r\n      Surface.ROTATION_180: angle := 180;\r\n      Surface.ROTATION_270: angle := 270; \r\n    end;\r\n     \r\n    mCamera.DisplayOrientation := (mCameraInfo.orientation - angle + 360) mod 360;\r\n  end;\r\n<\/pre>\n<p>There are a lot of assumptions here.<\/p>\n<p>First of all, there&#8217;s no error checking so if something should unexpectedly go awry this app is simply going to crash out ungracefully.  This isn&#8217;t an exercise in implementing a fully robust app for use on any and all Android devices though, so again, I can get away with it here.<\/p>\n<p>Similarly, changing the camera <strong>DisplayOrientation<\/strong> is something that on older versions of Android (prior to SDK revision 14, Android 4.0) can only be done when there is no active preview.  On the version of Android on my device &#8211; 4.1.2 &#8211; this isn&#8217;t an issue.  But to be safe I could wrap this method in code which stops the preview (<strong>mCamera.stopPreview<\/strong>) and then restarts it only after applying the orientation change (re-assigning <strong>PreviewDisplay<\/strong> and calling <strong>startPreview<\/strong> again).<\/p>\n<p>In the real world this would be performed conditionally, following a test of the Android version on which the app is running.<\/p>\n<p>But for now, this is almost all I need for a functioning camera preview in my app.  The only step remaining at this point is to actually instantiate the <strong>ViewFinder<\/strong> class and place it in the <strong>FrameLayout<\/strong> container we created at the beginning.<\/p>\n<h3>Creating The ViewFinder<\/h3>\n<p>So, returning back to the <strong>MainActivity<\/strong> class we already have, we add two new members.  One will hold a reference to the <strong>Camera<\/strong>, the other to the <strong>Viewfinder<\/strong>.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n    MainActivity = public class(Activity, Camera.PictureCallback)\r\n    private\r\n      mCamera: Camera;\r\n      mViewFinder: ViewFinder;\r\n    public\r\n      method onCreate(savedInstanceState: Bundle); override;\r\n      method CaptureClick(v: View);\r\n    end;\r\n<\/pre>\n<p>I do not need to add anything to the uses clause of the <strong>MainActivity<\/strong> unit.  The <strong>ViewFinder<\/strong> class is declared in the same namespace as the <strong>MainActivity<\/strong> class already &#8211; <em>nz.co.deltics.demo.camera<\/em> &#8211; even though it is in a separate file.<\/p>\n<p>Finally, we need to add a little code to the <strong>onCreate<\/strong> method to turn on the <strong>Camera<\/strong>, instantiate the <strong>ViewFinder<\/strong> and place it in our UI.<\/p>\n<pre class=\"brush: oxygene; title: ; notranslate\" title=\"\">\r\n  method MainActivity.onCreate(savedInstanceState: Bundle);\r\n  var\r\n    layout: FrameLayout;\r\n    btnCapture: Button;\r\n  begin\r\n    inherited;\r\n\r\n    ContentView := R.layout.main;\r\n\r\n    mCamera := Camera.open;\r\n    mViewFinder := new ViewFinder(self, mCamera);\r\n\r\n    layout := FrameLayout(findViewById(R.id.camera_preview));\r\n    layout.addView(mViewFinder);\r\n\r\n    btnCapture := Button(findViewById(R.id.button_capture));\r\n    btnCapture.OnClickListener := new interface View.OnClickListener(onClick := @CaptureClick);\r\n  end;\r\n<\/pre>\n<p>After turning the <strong>Camera<\/strong> on with the call to <strong>open<\/strong> (without a specific camera ID, this opens the default camera &#8211; again, quick and dirty but adequate for my purposes) we create our <strong>ViewFinder<\/strong>, passing the reference to the Camera that the <strong>ViewFinder<\/strong> needs.<\/p>\n<p>We then obtain a reference to the FrameLayout we placed in our layout and add the ViewFinder view that we just created.<\/p>\n<p>That&#8217;s it.<\/p>\n<h3>All Over Bar The Shouting<\/h3>\n<p>In this state, this application is a functioning camera preview app.<\/p>\n<p>The only thing it lacks is the ability to actually take a picture, which is what we will look at in the next and final (in this series) instalment.<\/p>\n","protected":false},"excerpt":{"rendered":"<p><span class=\"rt-reading-time\" style=\"display: block;\"><span class=\"rt-label rt-prefix\">[Estimated Reading Time: <\/span> <span class=\"rt-time\">8<\/span> <span class=\"rt-label rt-postfix\">minutes]<\/span><\/span> Not a Merchant Ivory production, but Part 3 in the Oxygene for Java camera app for Android series. So far we have seen that we can work directly with the Android platform manifest and layout files and how the Oxygene language is a first class citizen in the Java platform and just one way in [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"jetpack_post_was_ever_published":false,"jetpack_publicize_message":"","jetpack_is_tweetstorm":false,"jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":[]},"categories":[212,205,4,180],"tags":[153,217,181],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_shortlink":"https:\/\/wp.me\/p1TKYv-qm","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":1624,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1624\/","url_meta":{"origin":1634,"position":0},"title":"Exploring Listeners With Oxygene","date":"16 Sep 2013","format":false,"excerpt":"Part 2 in a short series demonstrating the development of a simple camera app for Android using Oxygene. In the previous instalment we looked at the basic framework of our app. For this instalment I was going to show how to implement the camera preview or viewfinder for this instalment,\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1605,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1605\/","url_meta":{"origin":1634,"position":1},"title":"Anatomy of a Camera App","date":"15 Sep 2013","format":false,"excerpt":"Part 1 in an as yet unknown number of articles using a (very) simple camera application to demonstrate building first class Android applications using \"Pascal for Java\" - i.e. Oxygene Cooper. In this first instalment we will look at the basics - the components we are going to need and\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screen-Shot-2013-09-15-at-10.12.32-.png?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":1713,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1713\/","url_meta":{"origin":1634,"position":2},"title":"How to Call Java Code from an Oxygene Android Application","date":"20 Sep 2013","format":false,"excerpt":"Lachlan just posted a link to a post on Google+ (also available as a PDF) demonstrating how to call Java from Delphi XE5. I was shocked at both the amount and the nature of the code involved. It is long, convoluted and ugly stuff (nb. that isn't a criticism of\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1658,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1658\/","url_meta":{"origin":1634,"position":3},"title":"Crash Bang Wallop, What a Picture!","date":"19 Sep 2013","format":false,"excerpt":"The fourth and final part in the not-as-short-as-I-thought-it-would be series on building a camera app for Android using Oxygene. In this penultimate instalment we will add the capability to actually take a picture. But that won't take very long, so then we will spend a bit of time tidying up\u2026","rel":"","context":"In &quot;Android&quot;","img":{"alt_text":"Very Ugly Duckling","src":"https:\/\/i0.wp.com\/www.deltics.co.nz\/blog\/wp-content\/uploads\/Screenshot_2013-09-19-19-38-56-1024x640.jpg?resize=350%2C200&ssl=1","width":350,"height":200},"classes":[]},{"id":2202,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/2202\/","url_meta":{"origin":1634,"position":4},"title":"What is Hydrogene ?  Asked and Answered","date":"03 Dec 2013","format":false,"excerpt":"Over the past few weeks there has been some speculation as to what the mysterious \"Hydrogene\" that RemObjects have been working on may or may not be. Well, that particular feline has slipped it's captors and escaped the bag. I'm not aware of any official announcement and I hope that\u2026","rel":"","context":"In &quot;Delphi&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]},{"id":1503,"url":"https:\/\/www.deltics.co.nz\/blog\/posts\/1503\/","url_meta":{"origin":1634,"position":5},"title":"Sharing Code Across Platforms in Oxygene","date":"22 Aug 2013","format":false,"excerpt":"There seems to be a perception among some people that Delphi is in the unique position of allowing developers to share and re-use code across the various platforms that it's compiler can now (and will soon) target. But this is not the case. Oxygene has had this capability right from\u2026","rel":"","context":"In &quot;Cooper&quot;","img":{"alt_text":"","src":"","width":0,"height":0},"classes":[]}],"_links":{"self":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1634"}],"collection":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/comments?post=1634"}],"version-history":[{"count":12,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1634\/revisions"}],"predecessor-version":[{"id":1647,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/posts\/1634\/revisions\/1647"}],"wp:attachment":[{"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/media?parent=1634"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/categories?post=1634"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.deltics.co.nz\/blog\/wp-json\/wp\/v2\/tags?post=1634"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}